JUC源码分析-辅助类-CountDownLatch,Semaphore,CyclicBarrier,Exchanger

本文分析并发辅助类 CountDownLatch,Semaphore,CyclicBarrier,Exchanger

CountDownLatch

同步辅助类 实现一个线程 等待其他1~N个线程执行完成,再继续执行其他代码。

构造方法

跟踪构造方法 依赖一个 内部类,这个内部类Sync 继承了AbstractQueuedSynchronizer

public CountDownLatch(int count) {

if (count < 0) throw new IllegalArgumentException("count < 0");

this.sync = new Sync(count);

}

Sync(int count) {

setState(count);

}

 

CountDown依赖 AQS 实现。

构造方法设置state=n执行 await()进入等待,每次执行 countdown state=state-1 当state=0 执行唤醒,await()后面代码继续执行。

实现原理比较简单,依赖AQS共享锁实现,重写了tryReleaseShared 和tryAcquireShared。这里就不讲解了,贴出跟踪代码,很容易看明白。

countDown

public void countDown() {

sync.releaseShared(1);

}

AQS-

public final boolean releaseShared(int arg) {

if (tryReleaseShared(arg)) {

doReleaseShared();

return true;

}

return false;

}

CountDownLatch-Sync-countDown

protected boolean tryReleaseShared(int releases) {

// Decrement count; signal when transition to zero

for (;;) {

int c = getState();

if (c == 0)

return false;

int nextc = c-1;

if (compareAndSetState(c, nextc))

return nextc == 0;

}

}

}

 

public void await() throws InterruptedException {

sync.acquireSharedInterruptibly(1);

}

public final void acquireSharedInterruptibly(int arg)

throws InterruptedException {

if (Thread.interrupted())

throw new InterruptedException();

if (tryAcquireShared(arg) < 0)

doAcquireSharedInterruptibly(arg);

}

CountDownLatch-Sync

protected int tryAcquireShared(int acquires) {

return (getState() == 0) ? 1 : -1;

}

总结:CountDownLatch 依赖AQS 共享锁实现,实现比较简单,使用了内部类继承AQS的传统方式,重写了tryReleaseShared 和tryAcquireShared。

 

Semaphore

Semaphore:依赖AQS 共享锁的典型实现

CountDownLatch与Semaphore都依赖AQS共享锁实现,比较二者的实现,一定能总结出有意义的经验。

CountDownLatch

tryAcquireShared: state等于0返回1,不等于0返回-1 (1代表成功,-1代表失败)

tryReleaseShared: 每次执行 state减1,CAS更新state,如果失败循环重试,如果成功根据 state==0 返回true或false

Semaphore

tryAcquireShared: 每次执行 state减1 , 如果剩余值<0,返回负数,CAS更新state,如果失败循环重试,如果成功返回大于或等于0的数。 (大于等于0代表成功,负数代表失败)

tryReleaseShared: 每次执行 state加1 , next<curren抛出异常,CAS更新state,如果失败循环重试,如果成功返回true 。

CountDownLatch获得锁的条件是通行证达到固定条件。 Semaphore获得的锁的条件 是获取多个通行证中的一个。

 

 

CyclicBarrier

协同辅助类,可以使多个线程相互等待,直到到达了某个公共屏障点(common barrier point )。

实现逻辑是在指定数量的线程执行了await方法后。这些线程才会继续执行。

 

CountDownLatch和CyclicBarrier的区别:

  1. CountDownLatch的作用是允许N个线程等待其他指定数目的线程完成执行,这些指定数目的执行不会等待。而CyclicBarrier则是允许N个线程相互等待。
  2. CountDownLatch的计数器无法被重置;CyclicBarrier的计数器可以被重置后使用,因此它被称为是循环的barrier。

详细解释下第一点

假设有 A,B,C 三个任务,CountDownLatch 是C等待 A ,B执行完成后 再执行, A ,B不等待C执行,用于多个任务有先后顺序的场景。CyclicBarrier 是A,B,C 相互等待完成第一部分任务后执行后续任务,用于多个任务必须同时达到某种条件才能往下执行的场景。

 

类继承结构:没有继承类

构造方法分析:

public CyclicBarrier(int parties) {

this(parties, null);

}

 

public CyclicBarrier(int parties, Runnable barrierAction) {

if (parties <= 0) throw new IllegalArgumentException();

this.parties = parties;// 需要相互等待的线程数

this.count = parties;//初始化count

this.barrierCommand = barrierAction;/

}

重要属性分析:

//处理阻塞用的锁

private final ReentrantLock lock = new ReentrantLock();

//用来处理等待和唤醒的 Condition

private final Condition trip = lock.newCondition();

//需要相互等待的线程数

private final int parties;

//达到 公共屏障要执行的动作

private final Runnable barrierCommand;

//代表一轮 barrier的状态

private Generation generation = new Generation();

//还需要等待的线程数。

private int count;

Generation :代表每执行一轮等待和释放的状态, 终止 状态改变,开始下一轮创建新的实例。

dowait

await 依赖dowait实现, dowait是CyclicBarrier核心方法。

barrierCommand:通过这个属性 实现在相互等待解除前一刻执行自定义的动作。

执行逻辑:

假设parties==3, 当执行 await的线程数小于2时,执行 Condition.await方法等待唤醒,当await的线程数等于3,执行Condititon.signalAll唤醒等待的线程。

 

private int dowait(boolean timed, long nanos)

throws InterruptedException, BrokenBarrierException,

TimeoutException {

final ReentrantLock lock = this.lock;

lock.lock();//加锁,所有的使用CyclicBarrier的线程使用同一个锁,性能消耗大。

try {

final Generation g = generation;

 

if (g.broken)// 其他线程执行中被打断 执行了breakBarrier()后,g.broken==true

throw new BrokenBarrierException();

//打断处理

if (Thread.interrupted()) {//代码块A

// generation.broken = true;count = parties;trip.signalAll();

//更新generation状态,恢复count,唤醒所有的等待线程

breakBarrier();

throw new InterruptedException();

}

//记录还有多少没有执行 到此的线程

int index = --count;

if (index == 0) { // tripped // 执行的线程数都执行了,达到屏障条件,触发解除等待动作

boolean ranAction = false;

try {

final Runnable command = barrierCommand;

if (command != null)//如果有需要执行的动作,do it

command.run();

ranAction = true;

nextGeneration();// 唤醒等待的线程,恢复count,生成新的 generation,。

return 0;

} finally {

if (!ranAction)

breakBarrier();// 这里只有 command.run() 发生异常 才有可能执行。

}

}

 

// loop until tripped, broken, interrupted, or timed out

//循环直到 解除等待,打断,或超时

for (;;) {

try {

if (!timed)

trip.await();// 非超时 处理,执行等待 直到被唤醒。

else if (nanos > 0L)

nanos = trip.awaitNanos(nanos); // 超时等待 代码B

} catch (InterruptedException ie) {

//打断处理

if (g == generation && ! g.broken) {//正常情况下 此判断成立

breakBarrier();

throw ie;

} else { // 如果其他线程也被打断 执行了代码块A,就会执行到这里

//记录打断状态 方便后续执行

Thread.currentThread().interrupt();

}

}

 

 

if (g.broken)

//执行上面代码块中 catch,或 执行到此时其他线程被打断执行了代码块A

//都可能执行到此

throw new BrokenBarrierException();

if (g != generation)// 解除等待。

//参与的线程都没有被打断或执行异常情况,都会执行到此

return index;

 

if (timed && nanos <= 0L) {//执行代码B超时

breakBarrier();

throw new TimeoutException();

}

}

} finally {

lock.unlock();

}

}

要点分析:dowait()是await()的实现函数,它的作用就是让当前线程阻塞,直到“有parties个线程到达barrier” 或 “当前线程被中断” 或 “超时”这3者之一发生,当前线程才继续执行。当所有parties到达barrier(count=0),如果barrierCommand不为空,则执行barrierCommand。然后调用nextGeneration()进行换代操作。

在for(;;)自旋中。timed是用来表示当前是不是“超时等待”线程。如果不是,则通过trip.await()进行等待;否则,调用awaitNanos()进行超时等待

CyclicBarrier通过独占锁Reentrantlock和Condition配合实现。

 

 

Exchanger

同步辅助类,让两个线程可以交换数据。

关键技术点1:CacheLine填充

交换数据的场所就是Slot,每个要进行数据交换的线程在内部会用一个Node来表示。

Slot其实就是一个AtomicReference,其里面的q0, q1,..qd那些变量,都是多余的,不用的,起到了cache line填充的作用,避免了伪共享问题;

伪共享说明:假设一个类的两个相互独立的属性a和b在内存地址上是连续的(比如FIFO队列的头尾指针),那么它们通常会被加载到相同的cpu cache line里面。并发情况下,如果一个线程修改了a,会导致整个cache line失效(包括b),这时另一个线程来读b,就需要从内存里再次加载了,这种多线程频繁修改ab的情况下,虽然a和b看似独立,但它们会互相干扰,非常影响性能。

关键技术点2:锁分离

同ConcurrentHashMap类型,Exchange没有只定义一个slot,而是定义了一个slot的数组。这样在多线程调用exchange的时候,可以各自在不同的slot里面进行匹配

数据结构分析

 

Slot数组结构如上图所示

Slot和Note都继承了AtomicReference,维护value。 Slot中的value是Note,Node中的value 是要交换的数据。 Note中的waiter保存被阻塞的线程,item用于临时保存交换数据。

doExchange分析

doExchange处理步骤

  1. 根据线程id获取slot数组index,如果这个位置的slot是null,创建slot.
  2. 如果slot中的数据不为null,交换数据,解除等待交换线程的阻塞。
  3. 如果slot中的数据为null,占据当前slot,自旋转等待接其他线程交换数据,自旋结束还没得到数据,失效原index数据, index折半继续匹配。
  4. 当index==0 进入阻塞 等待唤醒,其他线程也会挪动到此,如果运气不好,最后所有线程都会在index==0位置交换数据。

注意Node有value和item两个属性, 当执行步骤3时 ,获取node.value, 执行步骤2时将node.item 赋值给value。

 

private Object doExchange(Object item, boolean timed, long nanos) {

Node me = new Node(item); //创建 占据节点

int index = hashIndex(); //计算当前index

int fails = 0; // 统计失败次数

 

for (;;) {

Object y; // 当前 slot内容

Slot slot = arena[index];

if (slot == null) //初始化当前slot

createSlot(index); //循环重读

else if ((y = slot.get()) != null && // 匹配上了,尝试交换数据,

slot.compareAndSet(y, null)) {//将slot 中的y取出,防止其他线程抢先

Node you = (Node)y;

if (you.compareAndSet(null, item)) {//交换 item

LockSupport.unpark(you.waiter);//解除阻塞

return you.item;//返回交换数据

} // 交换失败,说明 you被取消,continue重试

}

else if (y == null && // 没匹配上,占据当前slot.

slot.compareAndSet(null, me)) {

if (index == 0) // 当前index=0 阻塞

return timed ?

awaitNanos(me, slot, nanos) :

await(me, slot);

Object v = spinWait(me, slot); // 自旋等待匹配

if (v != CANCEL)//自旋中匹配上了

return v;//返回匹配

me = new Node(item); // 丢弃取消节点

int m = max.get();

if (m > (index >>>= 1)) // 折半查找

max.compareAndSet(m, m - 1); // Maybe shrink table

}

else if (++fails > 1) { // 失败次数多 调整 index

int m = max.get();

if (fails > 3 && m < FULL && max.compareAndSet(m, m + 1))

index = m + 1; // Grow on 3rd failed slot

else if (--index < 0)

index = m; // Circularly traverse

}

}

}

注意方法中的技巧,执行匹配和待匹配前,都要执行占位,匹配占位执行 slot.compareAndSet(y, null)

待匹配占位执行 slot.compareAndSet(null, me)

private static boolean tryCancel(Node node, Slot slot) {

 

if (!node.compareAndSet(null, CANCEL))//将Node的value从null替换为CANCEL

return false;

if (slot.get() == node) // pre-check to minimize contention

slot.compareAndSet(node, null);//将当前index对应Slot的value替换为null

return true;

}

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值