CountDownLatch、CyclicBarrier和 Semaphore,Exchanger
CountDownLatch
类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。
构造方法
countDownLatch=new CountDownLatch(int count)
countDownLatch.countDown()每次count减一(在其他的每个子线程中)
countDownLatch.await()等待其他线程把count减为0(在主线程中)
它内部使用的同步器继承自AQS(它这里用到的是共享锁)
private static final class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 4982264981922014374L; Sync(int count) { setState(count); } //获取同步状态 int getCount() { return getState(); } //tryAcquireShared()的作用是尝试获取共享锁。
如果"锁计数器=0",即锁是可获取状态,则返回1;否则,锁是不可获取状态,则返回-1。 protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; } //释放同步状态 protected boolean tryReleaseShared(int releases) { for (;;) { int c = getState(); if (c == 0) return false;//同步状态为0,没有需要释放的线程 int nextc = c-1; if (compareAndSetState(c, nextc)) return nextc == 0; } } }
await():CountDownLatch提供await()方法来使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
await其内部使用AQS的acquireSharedInterruptibly(int arg):
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0)//需要我们重写的,表示共享锁状态不为0 doAcquireSharedInterruptibly(arg);
}
tryAcquireShared(arg)中的getState()获取同步状态,其值等于计数器的值,从这里我们可以看到如果计数器值不等于0,则会调用doAcquireSharedInterruptibly(int arg),该方法为一个自旋方法会尝试一直去获取同步状态(等待state变为0),直到该线程获取到共享锁或被中断才返回:
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
//创建“当前线程”的Node节点,且Node中记录的锁是“共享锁”类型,并将该节点到CLH队列末尾 final Node node = addWaiter(Node.SHARED); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head) { /** * 对于CountDownLatch而言,如果计数器值不等于0,那么r 会一直小于0 */ int r = tryAcquireShared(arg); if (r >= 0) { setHeadAndPropagate(node, r); p.next = null; // help GC failed = false; return; } } //等待,阻塞 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }
CountDownLatch提供countDown() 方法递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
public void countDown() { sync.releaseShared(1); }
内部调用AQS的releaseShared(int arg)方法来释放共享锁同步状态:
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) {//自己重写的,只有把state减为0才会返回true doReleaseShared(); //释放锁,唤醒await()中阻塞的线程,去重新尝试获取锁状态 return true; } return false; }
总结:
CountDownLatch是通过“共享锁”实现的。在创建CountDownLatch中时,会传递一个int类型参数count,该参数是“锁计数器”的初始状态,表示该“共享锁”最多能被count给线程同时获取。当某线程调用该CountDownLatch对象的await()方法时,该线程会等待“共享锁”可用时,才能获取“共享锁”进而继续运行。而“共享锁”可用的条件,就是“锁计数器”的值为0!而“锁计数器”的初始值为count,每当一个线程调用该CountDownLatch对象的countDown()方法时,才将“锁计数器”-1;通过这种方式,必须有count个线程调用countDown()之后,“锁计数器”才为0,而前面提到的等待线程才能继续运行!
以上,就是CountDownLatch的实现原理。
CyclicBarrier
和上面那个差不多,都是等待一组线程都开始等待以后,执行自己的线程,内部是使用ReentrantLock和Condition,主要内部有一个count记录等待线程的数量
CyclicBarrier可以用于多线程计算数据,最后合并计算结果的应用场景。比如我们用一个Excel保存了用户所有银行流水,每个Sheet保存一个帐户近一年的每笔银行流水,现在需要统计用户的日均银行流水,先用多线程处理每个sheet里的银行流水,都执行完之后,得到每个sheet的日均银行流水,最后,再用barrierAction用这些线程的计算结果,计算出整个Excel的日均银行流水。
构造方法barrier=new CyclicBarrier(int count ,Runnable barrierAction)
thread1.await()
thread2.await()
都在等待,达到数目以后执行barrierAction
CyclicBarrier是底层通过ReentrantLock(独占锁)和Condition来实现的。
await()函数:
public int await() throws InterruptedException, BrokenBarrierException { try { return dowait(false, 0L); } catch (TimeoutException toe) { throw new Error(toe); // cannot happen; } }
dowait()方法:
private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException { final ReentrantLock lock = this.lock; // 获取“独占锁(lock)” lock.lock(); try { // 保存“当前的generation” final Generation g = generation; // 若“当前generation已损坏”,则抛出异常。 if (g.broken) throw new BrokenBarrierException(); // 如果当前线程被中断,则通过breakBarrier()终止CyclicBarrier,唤醒CyclicBarrier中所有等待线程。 if (Thread.interrupted()) { breakBarrier(); throw new InterruptedException(); } // 将“count计数器”-1 int index = --count; // 如果index=0,则意味着“有parties个线程到达barrier”。 if (index == 0) { // tripped boolean ranAction = false; try { // 如果barrierCommand不为null,则执行该动作。 final Runnable command = barrierCommand; if (command != null) command.run(); ranAction = true; // 唤醒所有等待线程,并更新generation。 nextGeneration(); return 0; } finally { if (!ranAction) breakBarrier(); } } // 当前线程一直阻塞,直到“有parties个线程到达barrier” 或 “当前线程被中断” 或 “超时”这3者之一发生, // 当前线程才继续执行。 for (;;) { try { // 如果不是“超时等待”,则调用awati()进行等待;否则,调用awaitNanos()进行等待。 if (!timed) trip.await(); else if (nanos > 0L) nanos = trip.awaitNanos(nanos); } catch (InterruptedException ie) { // 如果等待过程中,线程被中断,则执行下面的函数。 if (g == generation && ! g.broken) { breakBarrier(); throw ie; } else { Thread.currentThread().interrupt(); } } // 如果“当前generation已经损坏”,则抛出异常。 if (g.broken) throw new BrokenBarrierException(); // 如果“generation已经换代”,则返回index。 if (g != generation) return index; // 如果是“超时等待”,并且时间已到,则通过breakBarrier()终止CyclicBarrier,唤醒CyclicBarrier中所有等待线程。 if (timed && nanos <= 0L) { breakBarrier(); throw new TimeoutException(); } } } finally { // 释放“独占锁(lock)” lock.unlock(); } }
参考:https://www.cnblogs.com/skywang12345/p/3533995.html
CyclicBarrier和CountDownLatch的区别
1.CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset() 方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次。
CyclicBarrier还提供其他有用的方法,比如getNumberWaiting方法可以获得CyclicBarrier阻塞的线程数量。isBroken方法用来知道阻塞的线程是否被中断。
在这篇博客里面我们用到了CyclicBarrier可以便于我们理解它是如何重置的。
https://www.cnblogs.com/smallJunJun/p/10540387.html
2.CountDownLatch的作用是允许1或N个线程等待其他线程完成执行;而CyclicBarrier则是允许N个线程相互等待,等大家都到了,然后同时唤醒所有的。
Semaphore:
Semaphore可以控同时访问的线程个数,它通过协调各个线程,以保证合理的使用公共资源。通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。它的内部包含一个公平锁和非公平锁,都是继承AQS的同步器
构造函数:设置许可的数量sema=new Semaphore(10),感觉它主要是可以控制你可以有多少个线程在操作。
sema.acquire()获取一个许可
sema.release()释放一个许可
在Java的并发包中,Semaphore类表示信号量。Semaphore内部主要通过AQS(AbstractQueuedSynchronizer)实现线程的管理。Semaphore有两个构造函数(默认好像就是非公平的),参数permits表示许可数,它最后传递给了AQS的state值。线程在运行时首先获取许可,如果成功,许可数就减1,线程运行,当线程运行结束就释放许可,许可数就加1。如果许可数为0,则获取失败,线程位于AQS的等待队列中,它会被其它释放许可的线程唤醒。在创建Semaphore对象的时候还可以指定它的公平性。一般常用非公平的信号量,非公平信号量是指在获取许可时先尝试获取许可,而不必关心是否已有需要获取许可的线程位于等待队列中,如果获取失败,才会入列。而公平的信号量在获取许可时首先要查看等待队列中是否已有线程,如果有则入列。
应用场景:
Semaphore可以用于做流量控制,特别公用资源有限的应用场景,比如数据库连接。假如有一个需求,要读取几万个文件的数据,因为都是IO密集型任务,我们可以启动几十个线程并发的读取,但是如果读到内存后,还需要存储到数据库中,而数据库的连接数只有10个,这时我们必须控制只有十个线程同时获取数据库连接保存数据,否则会报错无法获取数据库连接。
public class SemaphoreTest { private static final int THREAD_COUNT = 30; private static ExecutorService threadPool = Executors .newFixedThreadPool(THREAD_COUNT); private static Semaphore s = new Semaphore(10); public static void main(String[] args) { for (int i = 0; i < THREAD_COUNT; i++) { threadPool.execute(new Runnable() { @Override public void run() { try { s.acquire(); System.out.println("save data"); s.release(); } catch (InterruptedException e) { } } }); } threadPool.shutdown(); } }
Exchanger,用于两个线程间交换数据:
exgc =new Exchanger<String>();
分别在两个线程里面执行exchange方法,另外一个线程就能获得第一个线程exchange(str)里面的str
在Exchanger中,如果一个线程已经到达了exchanger节点时,对于它的伙伴节点的情况有三种:
1.如果它的伙伴节点在该线程到达之前已经调用了exchanger方法,则它会唤醒它的伙伴然后进行数据交换,得到各自数据返回。
2.如果它的伙伴节点还没有到达交换点,则该线程将会被挂起,等待它的伙伴节点到达被唤醒,完成数据交换。
3.如果当前线程被中断了则抛出异常,或者等待超时了,则抛出超时异常。
public class ThreadLocalTest { public static void main(String[] args) { Exchanger<List<Integer>> exchanger = new Exchanger<>(); new Consumer(exchanger).start(); new Producer(exchanger).start(); } } class Producer extends Thread { List<Integer> list = new ArrayList<>(); Exchanger<List<Integer>> exchanger = null; public Producer(Exchanger<List<Integer>> exchanger) { super(); this.exchanger = exchanger; } @Override public void run() { Random rand = new Random(); for(int i=0; i<10; i++) { list.clear(); list.add(rand.nextInt(10000)); list.add(rand.nextInt(10000)); list.add(rand.nextInt(10000)); list.add(rand.nextInt(10000)); list.add(rand.nextInt(10000)); try { list = exchanger.exchange(list); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } class Consumer extends Thread { List<Integer> list = new ArrayList<>(); Exchanger<List<Integer>> exchanger = null; public Consumer(Exchanger<List<Integer>> exchanger) { super(); this.exchanger = exchanger; } @Override public void run() { for(int i=0; i<10; i++) { try { list = exchanger.exchange(list); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.print(list.get(0)+", "); System.out.print(list.get(1)+", "); System.out.print(list.get(2)+", "); System.out.print(list.get(3)+", "); System.out.println(list.get(4)+", "); } } }
本文来自博客园,作者:LeeJuly,转载请注明原文链接:https://www.cnblogs.com/peterleee/p/10764831.html

浙公网安备 33010602011771号