当前位置:   article > 正文

并发基础4(JUC)_juc类图

juc类图

目录

1:什么是JUC

2:JUC和synchronize

2.1:JUC和synchronize关键字对比

2.2:juc简单用法

3:JUC特性和源码分析

3.1:构造器Lock lock = new ReentrantLock()

3.2:lock方法(公平锁和非公平锁)

3.3:unlock方法(公平锁和非公平锁没有区别)

3.4:AbstractQueuedSynchronizer源码分析和数据结构

4:JUC扩展

4.1:condition源码和案例(单向队列)

4.2:ReentrantReadWriteLock 分段锁,读写分离

4.3:AtomicInteger(ABA问题)

4.4:CountDownLatch(减法计数器)

4.5:Semaphore(信号量)

4.6:CyclicBarrier(栅栏)


1:什么是JUC

juc是java.util.concurrent包下的工具类,提供了跟synchronize相似的并发控制。在此包中增加了在并发编程中很常用的工具类。有了synchronize关键字,为什么还要学习JUC呢

2:JUC和synchronize

2.1:JUC和synchronize关键字对比

对比jucsynchronize
原理构成:工具类,通过API层面的锁来实现JVM关键字,底层编译后是monitorenter和monitorenter 关键字,锁定对象
使用方法:代码加锁,用户需要手动的加锁和释放锁,需要lock和unlock方法或者代码块加锁,不需要释放
是否可以中断:
方法可以中断,想执行就是执行,不想就等待reentrantLock.tryLock()尝试获取锁
不可以中断,要么执行,要么等待
是否公平: 可以公平也可以不公平不公平
知否重入:重入锁重入锁
是否能精准唤醒可以,绑定condition不可以,解锁后线程随机竞争
其他功能拓展

0:condition对象,指定唤醒

1:AtomicInteger元子类,线程安全

2:ReentrantReadWriteLock 分段锁,读写分离

3:CountDownLatch(减法计数器)

4:Semaphore(信号量)

5:CyclicBarrier(加法计数器)

6:ThreadPoolExecutor(线程池)

等等多种扩展功能

没有扩展功能

2.2:juc简单用法

1:构造ReentrantLock重入锁,默认非公平锁

2:使用lock加锁和unlock解锁,使用finally解锁

对比synchronize方法,代码控制

  1. /**
  2. * @author :huyiju
  3. * @date :8/5/21 5:05 PM
  4. * <p>
  5. * ReentrantLock重入锁
  6. * lock加锁 unlock解锁 使用try catch finally
  7. */
  8. public class LockT1 {
  9. public static void main(String[] args) {
  10. Ticks ticks = new Ticks();
  11. //线程1抢票
  12. new Thread(() -> {
  13. for (int i = 0; i < 20; i++) {
  14. ticks.sale();
  15. }
  16. }, "a").start();
  17. //线程2抢票
  18. new Thread(() -> {
  19. for (int i = 0; i < 20; i++) {
  20. ticks.sale();
  21. }
  22. }, "b").start();
  23. }
  24. }
  25. class Ticks {
  26. //票数20张
  27. int num = 20;
  28. //构造ReentrantLock重入锁
  29. Lock lock = new ReentrantLock();
  30. public void sale() {
  31. //加锁
  32. lock.lock();
  33. try {
  34. if (num > 0) {
  35. System.out.println("线程" + Thread.currentThread().getName() + "买了第" + (num--) + "张票");
  36. }
  37. } catch (Exception e) {
  38. e.printStackTrace();
  39. } finally {
  40. //解锁
  41. lock.unlock();
  42. }
  43. }
  44. }

以上就是juc加锁解锁实现线程安全的用法。我们要想知道具体的实现,为什么就能实现线程安全只需要查看源码就能知道了。 

3:JUC特性和源码分析

 在源码分析直前,我们首先要知道这个类图结构,后续的代码分析,也会加深这个结构理解

3.1:构造器Lock lock = new ReentrantLock()

1:Lock lock = new ReentrantLock();

通过构造器创建公平锁和非公平锁,我们暂时先不管这是什么意思,接着看第二步

  1. //第一步:无参构造创建非公平锁
  2. public ReentrantLock() {
  3. sync = new NonfairSync();
  4. }
  5. //第一步:有参构造创建锁 true公平锁 false非公平锁
  6. public ReentrantLock(boolean fair) {
  7. sync = fair ? new FairSync() : new NonfairSync();
  8. }

3.2:lock方法(公平锁和非公平锁)

lock.lock()加锁,这一步较为复杂,我们分解为多个步骤解析分为公平锁和非公平锁

1:非公平锁代码,加锁的过程中不管有多少个线程堵塞都会优先抢占资源

这里的核心难点是

  1. static final class NonfairSync extends Sync {
  2. //非公平锁加锁,首先就回进行CAS操作,操作AQS中State从0变1,然后执行该线程
  3. final void lock() {
  4. //if条件是cas 操作aqs中的state值,默认是0,0的话没有加锁,当前线程加锁后
  5. //将state的值变成1,其他的线程进来看到state已经是1的话,就等待。实现了
  6. //并发的线程安全
  7. if (compareAndSetState(0, 1))
  8. setExclusiveOwnerThread(Thread.currentThread());
  9. else
  10. acquire(1);
  11. }
  12. }

2:公平锁代码,加锁的过程中直接去排队

  1. static final class FairSync extends Sync {
  2. //公平锁,直接排队
  3. final void lock() {
  4. acquire(1);
  5. }
  6. }

3:公平锁和非公平锁acquire方法逻辑

  1. //*公平锁和非公平锁的acquire方法
  2. public final void acquire(int arg) {
  3. //此处有三个方法我们逐一解析
  4. if (!tryAcquire(arg) &&
  5. acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  6. selfInterrupt();
  7. }
  8. //方法1:tryAcquire方法此处公平锁和非公平锁存在差别
  9. protected final boolean tryAcquire(int acquires) {
  10. //获取当前线程
  11. final Thread current = Thread.currentThread();
  12. //获取aqs中的state
  13. int c = getState();
  14. //=0 表示没有线程,自己是第一个
  15. if (c == 0) {
  16. //此处代码
  17. // 非公平锁没有!hasQueuedPredecessors()这行代码,判断队列是否有数据,直接cas抢夺资源
  18. // 公平锁在此处直接看看队列有没有数据进行排队 若果不需要排队能进行CAS 则运行代码
  19. if (!hasQueuedPredecessors() &&
  20. compareAndSetState(0, acquires)) {
  21. setExclusiveOwnerThread(current);
  22. return true;
  23. }
  24. } else if (current == getExclusiveOwnerThread()) {
  25. //如果是当前线程是运行线程
  26. //将state的值+1,这里体现了可重入锁,对应上边代码的两个lock state=2
  27. int nextc = c + acquires;
  28. if (nextc < 0)
  29. throw new Error("Maximum lock count exceeded");
  30. setState(nextc);
  31. return true;
  32. }
  33. return false;
  34. }
  35. //方法2:addWaiter,将当前线程封装成node,
  36. // 放入由node组成的双向链表的最后,并且返回节点
  37. private Node addWaiter(Node mode) {
  38. Node node = new Node(Thread.currentThread(), mode);
  39. // Try the fast path of enq; backup to full enq on failure
  40. Node pred = tail;
  41. if (pred != null) {
  42. node.prev = pred;
  43. if (compareAndSetTail(pred, node)) {
  44. pred.next = node;
  45. return node;
  46. }
  47. }
  48. enq(node);
  49. return node;
  50. }
  51. //方法3:acquireQueue获取队列,将当前线程封装成node,
  52. final boolean acquireQueued(final Node node, int arg) {
  53. boolean failed = true;
  54. try {
  55. boolean interrupted = false;
  56. for (;;) {//死循环
  57. final Node p = node.predecessor();//获得该node的前一个节点
  58. //如果当前节点是第一个节点,代表排队第一个人
  59. //尝试tryAcquire获取锁,tryAcquire非公平锁和公平锁的代码不相同,代码复用
  60. if (p == head && tryAcquire(arg)) {
  61. setHead(node);
  62. p.next = null; // help GC,将前置节点的下个节点设置为空,这样就没有指针指向它,可以被gc回收
  63. failed = false;
  64. return interrupted;//返回false表示不能被打断,意思是没有被挂起,也就是获得到了锁
  65. }
  66. //不是第一个排队的
  67. //shouldParkAfterFailedAcquire这里表示将节点挂起,
  68. //并且将当前节点状态设置SIGNAL,不能放弃状态值CANCELLED=1的节点
  69. if (shouldParkAfterFailedAcquire(p, node) &&
  70. parkAndCheckInterrupt())//挂起线程 park() 等待unlock唤醒线程
  71. interrupted = true;
  72. }
  73. } finally {
  74. if (failed)
  75. cancelAcquire(node);
  76. //如果失败取消尝试获取锁(从上面的代码看只有进入p == head && tryAcquire(arg)这个逻辑是才会触发,
  77. // 这个时候前置节点正好在当前节点入队的时候执行完,当前节点正好获得锁,具体的代码以后分析)
  78. }
  79. }
  80. private final boolean parkAndCheckInterrupt() {
  81. LockSupport.park(this);
  82. return Thread.interrupted();//这里返回是否被打断,在lockInterruptibly中有意义,在lock中没有意义,个人理解只是为了代码复用。
  83. }

4:总结

1:通过构造器构建公平和非公平锁

2:在lock方法中 公平锁进来直接acquire,非公平锁CAS尝试回去资源然后acquire

3:acquire方法包含以下三个方法

  1. if (!tryAcquire(arg) &&
  2. acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
  3. selfInterrupt();

4:tryAcquire公平锁和非公平再次获取锁,有队就排是公平锁,不排队先尝试是非公平锁,都获取不到锁的时候执行addWaiter

5:addWaiter方法将节点添加到aqs中组成双向队列,通过aqs保证安全性。

6:acquireQueued再起尝试看看前边的线程执行完了没有(再起调用tryAcquire),如果自己是第一个排队的,执行自己的线程。没有的话LockSupport.park(this)暂停线程。

3.3:unlock方法(公平锁和非公平锁没有区别)

  1. //第一步unlock方法:释放锁,aqs中的state的状态值-1
  2. public void unlock() {
  3. sync.release(1);
  4. }
  5. //第二步release方法:首先执行执行tryRelease方法
  6. public final boolean release(int arg) {
  7. if (tryRelease(arg)) {//解锁成功
  8. AbstractQueuedSynchronizer.Node h = head;//获取头部节点 不为空和头部状态不是0
  9. if (h != null && h.waitStatus != 0)
  10. unparkSuccessor(h);//参数是头部节点
  11. return true;
  12. }
  13. return false;
  14. }
  15. //第三步tryRelease方法:解锁操作,将state-1 如说是0的话 返回true执行第四部唤醒方法
  16. protected final boolean tryRelease(int releases) {
  17. int c = getState() - releases;
  18. //不是的那个前线程释放锁,报异常
  19. if (Thread.currentThread() != getExclusiveOwnerThread())
  20. throw new IllegalMonitorStateException();
  21. boolean free = false;
  22. if (c == 0) {
  23. free = true;
  24. setExclusiveOwnerThread(null);//=0 解锁成功 线程设置为null
  25. }
  26. setState(c);
  27. return free;
  28. }
  29. //第四步unparkSuccessor唤醒方法:从头到尾的唤醒
  30. private void unparkSuccessor(AbstractQueuedSynchronizer.Node node) {
  31. int ws = node.waitStatus;
  32. if (ws < 0)
  33. compareAndSetWaitStatus(node, ws, 0);
  34. AbstractQueuedSynchronizer.Node s = node.next;
  35. if (s == null || s.waitStatus > 0) {
  36. s = null;
  37. for (AbstractQueuedSynchronizer.Node t = tail; t != null && t != node; t = t.prev)
  38. if (t.waitStatus <= 0)
  39. s = t;
  40. }
  41. if (s != null)//唤醒头部节点的下一个
  42. LockSupport.unpark(s.thread);
  43. }

总结:

unlock方法:对state-1的时候没有cas,而是判断线程是否是运行的线程,很聪明的做法,

1:尝试释放锁,将state的值重置为0,这里的原始值可能大于1是因为重入的原因

2:成功的话,解锁。unpark接下来的节点。从头到尾的唤醒。

3.4:AbstractQueuedSynchronizer源码分析和数据结构

1:源码分析,我们分析有效的源码可以知道下图的数据结构

  1. public class AbstractQueuedSynchronizer {
  2. private transient volatile AbstractQueuedSynchronizer.Node head;//链表的头部节点
  3. private transient volatile AbstractQueuedSynchronizer.Node tail;//链表的末尾节点
  4. private volatile int state;//状态值默认是0未使用,>1重入锁
  5. //aqs的内置静态类 也就是双向链表的结构
  6. static final class Node {
  7. static final int CANCELLED = 1;//线程取消
  8. static final int SIGNAL = -1;//线程等待中
  9. static final int CONDITION = -2;//线程在等待条件
  10. static final int PROPAGATE = -3;//暂时没有到
  11. volatile int waitStatus;//节点状态 默认是0
  12. volatile AbstractQueuedSynchronizer.Node prev;//前节点
  13. volatile AbstractQueuedSynchronizer.Node next;//后节点
  14. volatile Thread thread;//节点的线程
  15. }
  16. //此处省略各种修改state的值得方法和链表修改的方法
  17. }

2:抽象对列同步器的数据结构FIFO,头部插入尾部唤醒

4:JUC扩展

4.1:condition源码和案例(单向队列)

1:condition的类图        

如下图我们知道conditionObject是AQS的内部类,实现condition接口,我们查看图如下

 2:condition和object的区别

区别condition需要和ReentrantLock配合使用object的方法,需要和synchronize配合使用
线程阻塞awaitwait
线程唤醒

signal(唤醒队列的第一个)

signalAll(唤醒队列的全部,排队运行)

notify(随机一个)

notifyAll (唤醒全部,争抢锁)

3:案例代码使用

  1. /**
  2. * @author :huyiju
  3. * @date :8/5/21 5:05 PM
  4. *
  5. * 线程通信使用lock 和Condition接口
  6. *
  7. * Condition接口 实现循环唤醒
  8. */
  9. public class LockT4 {
  10. public static void main(String[] args) {
  11. TT2 tt = new TT2();
  12. new Thread(() -> {
  13. for (int i = 0; i < 3; i++) {
  14. tt.a1();
  15. }
  16. }, "aaa").start();
  17. new Thread(() -> {
  18. for (int i = 0; i < 3; i++) {
  19. tt.a2();
  20. }
  21. }, "bbb").start();
  22. new Thread(() -> {
  23. for (int i = 0; i < 3; i++) {
  24. tt.a3();
  25. }
  26. }, "ccc").start();
  27. }
  28. }
  29. class TT2 {
  30. int num = 1;
  31. Lock lock=new ReentrantLock();
  32. //此处设置三个condition,维护了3个condition队列
  33. //a1方法执行完唤醒a2
  34. //a2方法执行完唤醒a3
  35. Condition condition1=lock.newCondition();//需要和lock配合使用
  36. Condition condition2=lock.newCondition();
  37. Condition condition3=lock.newCondition();
  38. //开始num=1 执行a1 将num=2 唤醒a2
  39. public void a1() {
  40. lock.lock();
  41. try {
  42. while (num!=1){
  43. condition1.await();
  44. }
  45. num=2;
  46. System.out.println("当前线程:"+Thread.currentThread().getName());
  47. condition2.signal();//当前线程a,指定唤醒线程b
  48. } catch (Exception e) {
  49. e.printStackTrace();
  50. } finally {
  51. lock.unlock();
  52. }
  53. }
  54. //num=2 执行a2 将num=3 唤醒a3
  55. public void a2() {
  56. lock.lock();
  57. try {
  58. while (num!=2){
  59. condition2.await();
  60. }
  61. num=3;
  62. System.out.println("当前线程:"+Thread.currentThread().getName());
  63. condition3.signal();//当前线程b,指定唤醒线程c
  64. } catch (Exception e) {
  65. e.printStackTrace();
  66. } finally {
  67. lock.unlock();
  68. }
  69. }
  70. //num=3 执行a1 将num=1 唤醒a1
  71. public void a3() {
  72. lock.lock();
  73. try {
  74. while (num!=3){
  75. condition3.await();
  76. }
  77. num=1;
  78. System.out.println("当前线程:"+Thread.currentThread().getName());
  79. condition1.signal();//当前线程c,指定唤醒线程a
  80. } catch (Exception e) {
  81. e.printStackTrace();
  82. } finally {
  83. lock.unlock();
  84. }
  85. }
  86. }

4:源码分析

数据结构:单向队列

  1. //单向队列,firstNode-> node1 -> node2-> lastNode
  2. public class ConditionObject implements Condition, java.io.Serializable {
  3. /** 第一个节点 condition queue. */
  4. private transient AbstractQueuedSynchronizer.Node firstWaiter;
  5. /** 最后一个节点 condition queue. */
  6. private transient AbstractQueuedSynchronizer.Node lastWaiter;
  7. }

第一步:Condition condition1=lock.newCondition();通过lock接口创建了一个conditionObject,也就是aqs内部类。

第二步:condition1.await(),方法将node添加到单向队列,当前线程阻塞

  1. public final void await() throws InterruptedException {
  2. if (Thread.interrupted())
  3. throw new InterruptedException();
  4. //将节点添加到单向队列,返回该节点,节点元素之后线程ID和Node.CONDITION=-2
  5. //AbstractQueuedSynchronizer.Node node = new AbstractQueuedSynchronizer.Node(Thread.currentThread(), AbstractQueuedSynchronizer.Node.CONDITION);
  6. AbstractQueuedSynchronizer.Node node = addConditionWaiter();
  7. //释放资源 调用park方法
  8. int savedState = fullyRelease(node);
  9. int interruptMode = 0;
  10. // 如果当前节点不在【同步队列】中, 线程进入阻塞状态,等待被唤醒
  11. while (!isOnSyncQueue(node)) {
  12. LockSupport.park(this);
  13. if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
  14. break;
  15. }
  16. if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
  17. interruptMode = REINTERRUPT;
  18. if (node.nextWaiter != null) // clean up if cancelled
  19. unlinkCancelledWaiters();
  20. if (interruptMode != 0)
  21. reportInterruptAfterWait(interruptMode);
  22. }

第三步:condition2.signal()方法,唤醒队列的下一个或者全部

  1. public final void signal() {
  2. if (!isHeldExclusively())
  3. throw new IllegalMonitorStateException();
  4. AbstractQueuedSynchronizer.Node first = firstWaiter;//得到头部节点
  5. if (first != null)
  6. doSignal(first);//唤醒头部节点的下一个
  7. doSignalAll(first);//唤醒头部节点的之后的全部
  8. }

4.2:ReentrantReadWriteLock 分段锁,读写分离

1:ReentrantReadWriteLock作用

读的时候加读锁不让写,多个读线程可以同时读数据。

写的时候加写锁不让读,只能单个线程写。

能保证写的线程安全,又能保证读的效率高

2:案例使用

  1. public class ReadWriteLockT1 {
  2. public static void main(String[] args) {
  3. Book book = new Book();
  4. //此处for循环6个线程写数据,写入方法加锁
  5. for (int i = 1; i < 4; i++) {
  6. final int num = i;
  7. new Thread(() -> {
  8. book.write(num + "", num + "");
  9. }, String.valueOf(i)).start();
  10. }
  11. //for循环10个线程读数据,不需要加锁
  12. for (int i = 0; i < 5; i++) {
  13. final int num = i;
  14. new Thread(() -> {
  15. book.read(num + "");
  16. }, String.valueOf(i)).start();
  17. }
  18. }
  19. }
  20. class Book {
  21. Map<String, String> map = new HashMap();
  22. //第一步:使用ReentrantReadWriteLock
  23. ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  24. //读 必须加锁 防止读的时候写线程数据写入
  25. public void read(String key) {
  26. readWriteLock.readLock().lock();
  27. try {
  28. System.out.println("当前线程" + Thread.currentThread().getName() + "读-");
  29. map.get(key);
  30. System.out.println("当前线程" + Thread.currentThread().getName() + "读-完毕");
  31. } catch (Exception e) {
  32. e.printStackTrace();
  33. } finally {
  34. //读的时候解锁
  35. readWriteLock.readLock().unlock();
  36. }
  37. }
  38. //写 只能等到单个线程写完 才能下个线程接着写
  39. public void write(String key, String value) {
  40. readWriteLock.writeLock().lock();
  41. try {
  42. System.out.println("当前线程" + Thread.currentThread().getName() + "写");
  43. map.put(key, value);
  44. System.out.println("当前线程" + Thread.currentThread().getName() + "写完毕");
  45. } catch (Exception e) {
  46. e.printStackTrace();
  47. } finally {
  48. readWriteLock.writeLock().unlock();
  49. }
  50. }
  51. }

4.3:AtomicInteger(ABA问题)

1:AtomicInteger原子类作用

int a; a++ 难以保证多线程的数据安全。所有我们使用原子类,他的incrementAndGet方法是CAS操作,保证了线程安全。使用do while循环操作。其实还有很多方法都是CAS啦。但是CAS自旋z在多并发下会消耗CPU。

2:代码使用

  1. /**
  2. * @author :huyiju
  3. * @date :8/11/21 5:54 PM
  4. * 原子类的各种CAS操作验证
  5. */
  6. public class Cas1 {
  7. public static void main(String[] args) {
  8. //构造方法创建默认值 静态方法获取构造器传递的的内存偏移值
  9. AtomicInteger atomicInteger = new AtomicInteger(10);
  10. //compareAndSet 调用c++方法 传递(偏移量 旧值 新值)
  11. System.out.println("CAS赋值11是否成功:"+atomicInteger.compareAndSet(10, 11));
  12. System.out.println("CAS赋值11后的值:"+atomicInteger);
  13. //已经是11了 无法操作
  14. System.out.println("CAS赋值11是后,再次从10 -11赋值:"+atomicInteger.compareAndSet(10, 11));
  15. //+1操作
  16. System.out.println("CAS赋值11后再次自增:"+atomicInteger.incrementAndGet());
  17. }
  18. }

3:ABA问题代码使用AtomicStampedReference解决

  1. System.out.println("--------------原子引用ABA问题 初始化版本号是1-----------------");
  2. AtomicStampedReference atomicStampedReference = new AtomicStampedReference(128, 1);
  3. new Thread(() -> {
  4. System.out.println(atomicStampedReference.compareAndSet(128, 2, atomicStampedReference.getStamp(),
  5. atomicStampedReference.getStamp() + 1));
  6. System.out.println("捣乱线程2,值:" + atomicStampedReference.getReference());
  7. System.out.println(atomicStampedReference.compareAndSet(2, 128,
  8. atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
  9. System.out.println("捣乱线程2,值:" + atomicStampedReference.getReference());
  10. System.out.println("-------------");
  11. System.out.println("版本号是:"+atomicStampedReference.getStamp());
  12. System.out.println("捣乱线程2:" + atomicStampedReference.getReference());
  13. }).start();
  14. try {
  15. Thread.sleep(100);
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. System.out.println("--------------");
  20. System.out.println(atomicStampedReference.compareAndSet(1, 2, 1,
  21. 2));
  22. System.out.println("正经线程:" + atomicStampedReference.getReference());
  23. }

4.4:CountDownLatch(减法计数器)

1:CountDownLatch作用

主要是定义几个线程,执行一些操作,等这些操作完成之后在执行主线程

2:案例使用(主要是维护了aqs的state值,通过每次减一等于0的时候执行主线程)

  1. /**
  2. * 减法计数器
  3. *
  4. * CountDownLatch:
  5. *
  6. * 允许一个或多个线程等待其他一组线程完成操作,
  7. * 它使用一个计数器来初始化需要等待的线程数量,每当一个线程完成了它的任务,计数器就会递减,当计数器归零时,
  8. * 意味着所有需要等待的线程都已经完成了它们的任务,此时等待的线程(通常是一个或多个主线程)就可以继续执行了。
  9. *
  10. * 作用:
  11. * 确保一组线程都完成后才触发其他线程的执行,适用于资源加载、任务初始化等场景
  12. */
  13. public class CountDownLatchT1 {
  14. public static void main(String[] args) {
  15. //此处的计数器 计数器需要小于或者等于线程数 要不然countDownLatch.await() 永远不为0
  16. //第一步:将AQS中的state的值设为5
  17. CountDownLatch countDownLatch=new CountDownLatch(5);
  18. for (int i = 0; i < 4; i++) {
  19. new Thread(() -> {
  20. System.out.println("当前线程:"+Thread.currentThread().getName());
  21. System.out.println("当前线程做了很多实行,加载配置文件,读数据库等操作");
  22. countDownLatch.countDown();//每次state减1
  23. }, String.valueOf(i)).start();
  24. }
  25. try {
  26. countDownLatch.await();//等到计数器是0 才能向下执行
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. System.out.println("countdownlunch方法执行完毕,执行主线程");
  31. }
  32. }

4.5:Semaphore(信号量)

1:Semaphore作用

主要是限流,比如网站的流控,就像车道一样。多个线程限制执行小部分线程。也是操作state,将线程加入node的队列。park和unpark操作。

2:案例使用

  1. /**
  2. * 信号量
  3. *
  4. */
  5. public class SemaphoreTest1 {
  6. public static void main(String[] args) {
  7. Semaphore semaphore = new Semaphore(3);
  8. //创建5个线程,一共3个信号量,相当于3个车位
  9. //当开始的3个线程抢到信号量,可以停车,等着3个线程离开,剩下的2个线程,在抢车位
  10. for (int i = 0; i < 5; i++) {
  11. int finalI = i;
  12. new Thread(() -> {
  13. try {
  14. //第二步:获取state信号量 如果<0 线程加入队列park 否则线程执行
  15. semaphore.acquire();//相当于减1,车位减1
  16. System.out.println(Thread.currentThread().getName() + "抢到车位:"+ finalI);
  17. TimeUnit.SECONDS.sleep(3);
  18. System.out.println(Thread.currentThread().getName() + "离开车位:"+finalI);
  19. } catch (InterruptedException e) {
  20. throw new RuntimeException(e);
  21. }finally {
  22. semaphore.release();//用完之后+1,车位加回来
  23. }
  24. },"线程"+i).start();
  25. }
  26. System.out.println("Main主线程结束");
  27. }
  28. }

结果:

4.6:CyclicBarrier(栅栏)

1:CyclicBarrier作用,设置一个计数器比如是3,假如10个线程来了,三个一组进入执行。

 计数器必须和线程数一致
        栅栏的开口>线程 永远等待 人少了永远在门口等待
        栅栏的开口<线程 永远等待 人多了,多的人在门口永远等待,其他的人进去

2:案例使用

  1. /**
  2. * @author :huyiju
  3. * @date :8/9/21 6:23 PM
  4. * 栅栏 线程到达栅栏进行等待,等人齐了 在开始下一步执行
  5. */
  6. public static void main(String[] args) {
  7. //计数器必须和线程数一致
  8. //计数器>线程 永远等待 人少了永远在门口等待
  9. //计数器<线程 有的执行 人多了多的人在门口等待
  10. CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2, new Runnable() {
  11. @Override
  12. public void run() {
  13. System.out.println("栅栏每放行一批的,执行一次");
  14. }
  15. });
  16. for (int i = 0; i < 6; i++) {
  17. new Thread(() -> {
  18. try {
  19. //线程必须进入栅栏,等待全部的线程
  20. System.out.println("当前线程:" + Thread.currentThread().getName() + "进入栅栏");
  21. //线程进来将计数器-1,指导等于0,在执行,也就是进来4个线程才能执行
  22. cyclicBarrier1.await();
  23. } catch (InterruptedException e) {
  24. throw new RuntimeException(e);
  25. } catch (BrokenBarrierException e) {
  26. throw new RuntimeException(e);
  27. }finally {
  28. //cyclicBarrier1.reset();
  29. }
  30. System.out.println(Thread.currentThread().getName() + "--执行完毕");
  31. }).start();
  32. }
  33. }

案例分析:

 

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/神奇cpp/article/detail/966847
推荐阅读
相关标签
  

闽ICP备14008679号