Java java自行车官网

2023-09-13 01:00 22次浏览 知识

Java

文章目录

      • CountDownLatch闭锁
        • 相关介绍
        • 使用案例
        • 底层原理
      • CyclicBarrier栅栏
        • 相关介绍
        • 使用案例
        • 底层原理
      • 闭锁与栅栏
        • 二者区别

CountDownLatch闭锁
相关介绍

作用:就是一个或者一组线程在开始执行操作之前,必须要等到其他线程执行完才可以执行。我们举一个例子来说明,在考试的时候,老师必须要等到所有人交了试卷才可以走。此时老师就相当于等待线程,而学生就好比是执行的线程。

使用案例

CountDownLatch countDownLatch = new CountDownLatch(2);System.out.println("开始测试,测试人员就绪");new Thread(() -> { try { Thread.sleep(3000); System.out.println("测试人员 1 提交测试结果"); } catch (InterruptedException e) { e.printStackTrace(); }finally { countDownLatch.countDown(); }}).start();new Thread(() -> { try { Thread.sleep(7000); System.out.println("测试人员 2 提交测试结果"); } catch (InterruptedException e) { e.printStackTrace(); }finally { countDownLatch.countDown(); }}).start();new Thread(() -> { try { countDownLatch.await(); System.out.println("开始汇总测试结果,提交测试报告"); } catch (InterruptedException e) { e.printStackTrace(); }}).start();// 输出结果// 开始测试,测试人员就绪// 测试人员 1 提交测试结果// 测试人员 2 提交测试结果// 开始汇总测试结果,提交测试报告

底层原理

public void countDown() { sync.releaseShared(1);}
private static final class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 4982264981922014374L; Sync(int count) { setState(count); } int getCount() { return getState(); } protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; } 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)) // 如果当前状态值等于预期值 c,则以原子方式将同步状态设置为给定的更新值 nextc。 return nextc == 0; } }}
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1);}

CyclicBarrier栅栏
相关介绍

  • 栅栏类似于闭锁,它能阻塞一组线程直到某个事件的发生。栅栏与闭锁的关键区别在于,所有的线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待事件,而栅栏用于等待其他线程。
  • CyclicBarrier可以使一定数量的线程反复地在栅栏位置处汇集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用。

使用案例

public class CyclicBarriesTest { public static void main(String[] args) { int playerCount = 3; CyclicBarrier cyclicBarrier = new CyclicBarrier(playerCount); for (int i = 0; i < playerCount; i++) { Player player = new Player(cyclicBarrier); player.setName(String.valueOf(i)); player.start(); } } static class Player extends Thread { CyclicBarrier cyclicBarrier; public Player(CyclicBarrier cyclicBarrier) { this.cyclicBarrier = cyclicBarrier; //控制多个player使用同一个栅栏对象 } @Override public void run() { try { String name = Thread.currentThread().getName(); System.out.println("玩家 "+name+" 开始准备......"); int time = new Random().nextInt(10); Thread.sleep(time * 1000); System.out.println("玩家 "+name+" 准备就绪......"); cyclicBarrier.await(); //阻塞等待其他线程到达栅栏 System.out.println("玩家 "+name+" 进入游戏......"); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } } }}// 运行结果:// 玩家 0 开始准备......// 玩家 2 开始准备......// 玩家 1 开始准备......// 玩家 0 准备就绪......// 玩家 1 准备就绪......// 玩家 2 准备就绪......// 玩家 2 进入游戏......// 玩家 0 进入游戏......// 玩家 1 进入游戏......

底层原理

private final ReentrantLock lock = new ReentrantLock();private final Condition trip = lock.newCondition();private final int parties;private final Runnable barrierCommand;private Generation generation = new Generation();private int count;// CyclicBarrier内部使用了 ReentrantLock 和 Condition 两个类。它有两个构造函数: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; this.barrierCommand = barrierAction;}// 每个线程使用await()方法告诉CyclicBarrier我已经到达了栅栏处,然后当前线程被阻塞。public int await() throws InterruptedException, BrokenBarrierException { try { // 不超时等待 return dowait(false, 0L); } catch (TimeoutException toe) { throw new Error(toe); // cannot happen }}private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException { // 获取独占锁 final ReentrantLock lock = this.lock; lock.lock(); try { // 当前代 final Generation g = generation; // 如果这代损坏了,抛出异常 if (g.broken) throw new BrokenBarrierException(); // 如果线程中断了,抛出异常 if (Thread.interrupted()) { // 将损坏状态设置为true // 并通知其他阻塞在此栅栏上的线程 breakBarrier(); throw new InterruptedException(); } // 获取下标 int index = --count; // 如果是 0,说明最后一个线程调用了该方法 if (index == 0) { // tripped boolean ranAction = false; try { final Runnable command = barrierCommand; // 执行栅栏任务 if (command != null) command.run(); ranAction = true; // 更新一代,将count重置,将generation重置 // 唤醒之前等待的线程 nextGeneration(); return 0; } finally { // 如果执行栅栏任务的时候失败了,就将损坏状态设置为true if (!ranAction) breakBarrier(); } } // loop until tripped, broken, interrupted, or timed out for (;;) { try { // 如果没有时间限制,则直接等待,直到被唤醒 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(); } } // 当有任何一个线程中断了,就会调用breakBarrier方法 // 就会唤醒其他的线程,其他线程醒来后,也要抛出异常 if (g.broken) throw new BrokenBarrierException(); // g != generation表示正常换代了,返回当前线程所在栅栏的下标 // 如果 g == generation,说明还没有换代,那为什么会醒了? // 因为一个线程可以使用多个栅栏,当别的栅栏唤醒了这个线程,就会走到这里,所以需要判断是否是当前代。 // 正是因为这个原因,才需要generation来保证正确。 if (g != generation) return index; // 如果有时间限制,且时间小于等于0,销毁栅栏并抛出异常 if (timed && nanos <= 0L) { breakBarrier(); throw new TimeoutException(); } } } finally { // 释放独占锁 lock.unlock(); }}

闭锁与栅栏
二者区别

  1. 使用次数:CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置,可以使用多次,所以CyclicBarrier能够处理更为复杂的场景。
  2. 应用场景:CountDownLatch允许一个或多个线程等待一组事件的产生,而CyclicBarrier用于等待其他线程运行到栅栏位置。
  3. 提供方法:CyclicBarrier还提供了一些其他有用的方法,比如getNumberWaiting()方法可以获得CyclicBarrier阻塞的线程数量,isBroken()方法用来了解阻塞的线程是否被中断。

相关推荐