JUC是什么?
JUC就是java.util.concurrent
下面的类包,专门用于多线程的开发。
关于锁
传统锁synchronized
如下有三个线程A、B、C线程去争夺ticket资源,sale()
方法前面用synchronized
修饰以后,这个方法的调用对象 ticket
就被上锁了,每个线程去用这个对象调用sale()
方法的时候都会独立运行。如果你不加这个synchronized
就会出现A、B、C线程争夺资源的情况。
在这里,我们说 锁的本质就是:队列。 线程排队去获得CPU执行线程,执行完毕下一个线程上。
package org.example; /** * @author linghu * @date 2023/12/15 15:02 */ public class Demo01 { public static void main(String[] args) { final Ticket ticket=new Ticket(); //启动线程A new Thread(()->{ for (int i=0;i<40;i++){ ticket.sale(); } },"A").start(); new Thread(()->{ for (int i=0;i<40;i++){ ticket.sale(); } },"B").start(); new Thread(()->{ for (int i=0;i<40;i++){ ticket.sale(); } },"C").start(); } } class Ticket{ private int number=30; //卖票的方式 public synchronized void sale(){ if (number>0){ System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"张票剩余"+ number+"张票"); } } }
加了synchronized
运行如下图:
Lock锁
lock三部曲:
- 创建锁
final Ticket ticket=new Ticket();
- 加锁
lock.lock();
- 解锁
lock.unlock();
package org.example; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @author linghu * @date 2023/12/15 15:02 */ public class Demo01 { public static void main(String[] args) { final Ticket ticket=new Ticket(); //启动线程A new Thread(()->{ for (int i=0;i<40;i++){ ticket.sale(); } },"A").start(); new Thread(()->{ for (int i=0;i<40;i++){ ticket.sale(); } },"B").start(); new Thread(()->{ for (int i=0;i<40;i++){ ticket.sale(); } },"C").start(); } } //lock三部曲 class Ticket{ private int number=30; //1、创建锁 Lock lock=new ReentrantLock(); //卖票的方式 public synchronized void sale(){ //2、开启锁 lock.lock(); try { if (number>0){ System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"张票剩余"+ number+"张票"); } } catch (Exception e) { throw new RuntimeException(e); } finally { //3、关闭锁 lock.unlock(); } } }
上面的代码中,被lock三部曲锁上的代码,只能由一个线程执行。
synchronized和Lock的区别
生产者和消费者问题
synchronized版生产者和消费者问题
package org.example; /** * @author linghu * @date 2023/12/16 16:45 * A num+1 * B num-1 * 顺序:判断->业务->通知 */ public class A { public static void main(String[] args) { Data data = new Data(); //A:num+1 new Thread(()->{ for (int i=0;i<10;i++){ try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"A").start(); //B:num-1 new Thread(()->{ for (int i=0;i<10;i++){ try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"B").start(); } } class Data{ private int number=0; //+1 public synchronized void increment() throws InterruptedException { if (number!=0){ //等待 this.wait(); } number++; System.out.println(Thread.currentThread().getName()+"=>"+number); //通知其他线程,我-1完毕了 this.notify(); } //-1 public synchronized void decrement() throws InterruptedException { if (number==0){ //等待 this.wait(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); //通知其他线程,我-1完毕了 this.notify(); } }
注:如果有四个线程,就会出现假唤醒问题。
我们添加如下四个线程A、B、C、D:
package org.example; /** * @author linghu * @date 2023/12/16 16:45 * A num+1 * B num-1 * 顺序:判断->业务->通知 */ public class A { public static void main(String[] args) { Data data = new Data(); //A:num+1 new Thread(()->{ for (int i=0;i<10;i++){ try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"A").start(); //B:num-1 new Thread(()->{ for (int i=0;i<10;i++){ try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"B").start(); new Thread(()->{ for (int i=0;i<10;i++){ try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"C").start(); new Thread(()->{ for (int i=0;i<10;i++){ try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"D").start(); } } class Data{ private int number=0; //+1 public synchronized void increment() throws InterruptedException { if (number!=0){ //等待 this.wait(); } number++; System.out.println(Thread.currentThread().getName()+"=>"+number); //通知其他线程,我-1完毕了 this.notify(); } //-1 public synchronized void decrement() throws InterruptedException { if (number==0){ //等待 this.wait(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); //通知其他线程,我-1完毕了 this.notify(); } }
那么什么是虚假唤醒呢?
多线程环境下,有多个线程执行了wait()方法,需要其他线程执行notify()或者notifyAll()方法去唤醒它们,假如多个线程都被唤醒了,但是只有其中一部分是有用的唤醒操作,其余的唤醒都是无用功;对于不应该被唤醒的线程而言,便是虚假唤醒。
比如:仓库有货了才能出库,突然仓库入库了一个货品;这时所有的线程(货车)都被唤醒,来执行出库操作;实际上只有一个线程(货车)能执行出库操作,其他线程都是虚假唤醒。
为了防止虚假唤醒,我们在这里采用将if换为while:
package org.example; /** * @author linghu * @date 2023/12/16 16:45 * A num+1 * B num-1 * 顺序:判断->业务->通知 */ public class A { public static void main(String[] args) { Data data = new Data(); //A:num+1 new Thread(()->{ for (int i=0;i<10;i++){ try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"A").start(); //B:num-1 new Thread(()->{ for (int i=0;i<10;i++){ try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"B").start(); new Thread(()->{ for (int i=0;i<10;i++){ try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"C").start(); new Thread(()->{ for (int i=0;i<10;i++){ try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"D").start(); } } class Data{ private int number=0; //+1 public synchronized void increment() throws InterruptedException { while (number!=0){ //等待 this.wait(); } number++; System.out.println(Thread.currentThread().getName()+"=>"+number); //通知其他线程,我-1完毕了 this.notify(); } //-1 public synchronized void decrement() throws InterruptedException { while(number==0){ //等待 this.wait(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); //通知其他线程,我-1完毕了 this.notify(); } }
JUC版的生产者消费者问题
package org.example; /** * @author linghu * @date 2023/12/16 16:45 * A num+1 * B num-1 * 顺序:判断->业务->通知 */ public class B { public static void main(String[] args) { Data2 data = new Data2(); } } class Data2{ private int number=0; //+1 public void increment() throws InterruptedException { while (number!=0){ //等待 } number++; System.out.println(Thread.currentThread().getName()+"=>"+number); } //-1 public void decrement() throws InterruptedException { while (number==0){ //等待 } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); } }
我们用传统三剑客写的生产者消费者,现在把它删掉,准备用上面的代码进行改进,改成JUC版本的生产者消费者。
JUC的代码如下:
package org.example; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @author linghu * @date 2023/12/16 16:45 * A num+1 * B num-1 * 顺序:判断->业务->通知 */ public class B { public static void main(String[] args) { Data2 data2 = new Data2(); //A:num+1 new Thread(()->{ for (int i=0;i<10;i++){ try { data2.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"A").start(); //B:num-1 new Thread(()->{ for (int i=0;i<10;i++){ try { data2.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"B").start(); new Thread(()->{ for (int i=0;i<10;i++){ try { data2.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"C").start(); new Thread(()->{ for (int i=0;i<10;i++){ try { data2.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"D").start(); } } class Data2{ private int number=0; Lock lock=new ReentrantLock(); Condition condition= lock.newCondition(); //+1 public void increment() throws InterruptedException { lock.lock();//上锁操作 try { while (number!=0){ //等待 condition.await(); } //业务代码... number++; System.out.println(Thread.currentThread().getName()+"=>"+number); condition.signalAll();//通知其他线程,我+1完毕 } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock();//解锁操作 } } //-1 public void decrement() throws InterruptedException { lock.lock(); try { while (number==0){ //等待 condition.await(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
执行结果如下:
我们现在希望按照:A->B->C->D
的顺序去进行执行,精准通知唤醒我们的线程!
如下代码中,我们实现了A->B->C->D
的顺序,主要是靠 Condition
监控器,我们设置了三个监控器:
Condition conditionA = lock.newCondition(); Condition conditionB = lock.newCondition(); Condition conditionC = lock.newCondition();
唤醒方式如下:
public void weakA() { lock.lock(); try { while(num != 1) { conditionA.await(); } //业务代码... conditionB.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void weakB() { lock.lock(); try { while(num != 2) { conditionB.await(); } //业务代码... conditionC.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void weakC() { lock.lock(); try { while(num != 3) { conditionC.await(); } //业务代码... conditionA.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }
如下为完整代码:
package org.example; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /** * @author linghu * @date 2023/12/18 14:44 */ class Aweaken { ReentrantLock lock = new ReentrantLock(); int num = 1; Condition conditionA = lock.newCondition(); Condition conditionB = lock.newCondition(); Condition conditionC = lock.newCondition(); public void weakA() { lock.lock(); try { while(num != 1) { conditionA.await(); } num = 2; System.out.println("现在是线程 "+Thread.currentThread().getName()+", 下一个应该是线程B"); conditionB.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void weakB() { lock.lock(); try { while(num != 2) { conditionB.await(); } num = 3; System.out.println("现在是线程 "+Thread.currentThread().getName()+", 下一个应该是线程C"); conditionC.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void weakC() { lock.lock(); try { while(num != 3) { conditionC.await(); } num = 1; System.out.println("现在是线程 "+Thread.currentThread().getName()+", 下一个应该是线程A"); conditionA.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } public class C { public static void main(String[] args) { Aweaken b = new Aweaken(); new Thread(()->{ for (int i = 0; i < 10; i++) { b.weakA(); } },"A").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { b.weakB(); } },"B").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { b.weakC(); } },"C").start(); } }
执行效果:
8锁现象
在Java并发编程中,锁是一种关键的同步机制,用于控制多个线程对共享资源的访问。然而,在某些情况下,使用锁可能会引发性能问题,其中一个典型例子就是JUC中的8锁现象,也被称为锁粗化。
那何为8锁呢?
8锁现象是一种并发编程中的现象,主要涉及到Java中的synchronized
关键字。这个术语来源于对锁的八个问题的探讨,每个问题都围绕着锁的不同行为和效果展开。
- 标准情况下,两个线程先打印“发短信”还是“打电话”?
- “打电话”方法暂停4秒钟,两个线程先打印“发短信”还是“打电话”?
- 两个普通的锁方法,new一个对象调用,调用过程中间睡1秒,执行结果是什么?
- 两个普通的锁方法,new两个对象调用,调用过程中间睡1秒,执行结果是什么?
- 两个普通的锁方法,一个对象调用,一个类调用,调用过程中间睡1秒,执行结果是什么?
- 两个普通的锁方法,一个对象调用,一个类调用,但是类上加了synchronized关键字,调用过程中间睡1秒,执行结果是什么?
- 两个普通的锁方法,一个对象调用,一个类调用,但是类上加了synchronized关键字和方法上加了synchronized关键字,调用过程中间睡1秒,执行结果是什么?
- 两个普通的锁方法,一个对象调用,一个类调用,但是类上加了synchronized关键字和方法上加了synchronized关键字,但是方法上加了final关键字,调用过程中间睡1秒,执行结果是什么?
我们将上面的8个问题进行抽象:
案例一(先打印“发短信”)
- 标准情况下,两个线程先打印 发短信还是 打电话? 1/发短信 2/打电话。
- sendSms延迟4秒,两个线程先打印 发短信还是 打电话? 1/发短信 2/打电话。
public class Test1 { public static void main(String[] args) { Phone phone = new Phone(); //锁的存在 new Thread(()->{ phone.sendSms(); },"A").start(); // 捕获 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone.call(); },"B").start(); } } class Phone{ // synchronized 锁的对象是方法的调用者!、 // 两个方法用的是同一个锁,谁先拿到谁执行! public synchronized void sendSms(){ try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信"); } public synchronized void call(){ System.out.println("打电话"); } }
运行效果:
💧案例一总结:synchronized 锁的对象是方法的调用者,两个方法用的是同一个锁,谁先拿到谁执行。
案例二(先打印“打电话”)
- 增加了一个普通方法后!先执行发短信还是Hello? 普通方法。
- 两个对象,两个同步方法, 发短信还是 打电话? // 打电话
package org.example; import java.util.concurrent.TimeUnit; public class Test2 { public static void main(String[] args) { // 两个对象,两个调用者,两把锁! Phone2 phone1 = new Phone2(); Phone2 phone2 = new Phone2(); //锁的存在 new Thread(()->{ phone1.sendSms(); },"A").start(); // 捕获 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone2.call(); },"B").start(); } } class Phone2{ // synchronized 锁的对象是方法的调用者! public synchronized void sendSms(){ try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信"); } public synchronized void call(){ System.out.println("打电话"); } // 这里没有锁!不是同步方法,不受锁的影响 public void hello(){ System.out.println("hello"); } }
运行效果:
💧案例二总结:synchronized 锁的对象是方法的调用者,hello() 没有锁!不是同步方法,所以不受锁的影响。
案例三(先打印“发短信”)
增加两个静态的同步方法,只有一个对象,先打印 发短信?打电话?
两个对象!增加两个静态的同步方法, 先打印 发短信?打电话?
package org.example; import java.util.concurrent.TimeUnit; public class Test3 { public static void main(String[] args) { // 两个对象的Class类模板只有一个,static,锁的是Class Phone3 phone1 = new Phone3(); Phone3 phone2 = new Phone3(); //锁的存在 new Thread(()->{ phone1.sendSms(); },"A").start(); // 捕获 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone2.call(); },"B").start(); } } // Phone3唯一的一个 Class 对象 class Phone3{ // synchronized 锁的对象是方法的调用者! public static synchronized void sendSms(){ try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信"); } public static synchronized void call(){ System.out.println("打电话"); } }
运行效果:
💧案例三总结:两个对象的Class类模板只有一个。static 静态方法在类一加载就有了!锁的是Class。
案例四(先打印“打电话”)
1个静态的同步方法,1个普通的同步方法 ,一个对象,先打印 发短信?打电话?
1个静态的同步方法,1个普通的同步方法 ,两个对象,先打印 发短信?打电话?
package org.example; import java.util.concurrent.TimeUnit; public class Test4 { public static void main(String[] args) { // 两个对象的Class类模板只有一个,static,锁的是Class Phone4 phone1 = new Phone4(); Phone4 phone2 = new Phone4(); //锁的存在 new Thread(()->{ phone1.sendSms(); },"A").start(); // 捕获 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone2.call(); },"B").start(); } } // Phone3唯一的一个 Class 对象 class Phone4{ // 静态的同步方法 锁的是 Class 类模板 public static synchronized void sendSms(){ try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信"); } // 普通的同步方法 锁的调用者 public synchronized void call(){ System.out.println("打电话"); } }
运行效果:
💧案例四总结:静态的同步方法 锁的是 Class 类模板,普通的同步方法 锁的调用者。
new this 具体的一个手机
static Class 唯一的一个模板
集合不安全
所谓集合不安全,就是指:在高并发的情况下,创建线程使用集合会报异常!
我们看一下常用集合线程安全问题:
List
不安全ArrayList
不安全Set
不安全Map
不安全
List集合不安全
我们通过如下代码,循环30次创建线程,在线程中用到List集合,我还在业务代码那里 try-catch
一下,捕捉出现的异常。
/** * @author linghu * @date 2023/12/19 11:38 */ public class ListTest { public static void main(String[] args) { List<Object> arrayList = new ArrayList<>(); for (int i=1;i<=30;i++){ try { new Thread(()->{ arrayList.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(arrayList); },String.valueOf(i)).start(); } catch (Exception e) { e.printStackTrace(); } } } }
代码运行结果:
**结论:**List集合和ArrayList集合在多线程中使用不安全
List集合不安全的解决方案
我们可以采用 COW
的思想解决这个不安全问题。所谓 COW
是指 CopyOnWriteArrayList
:写入时复制!是计算机程序领域的一种优化策略。
解决方案:
new Vector<>()
new CopyOnWriteArrayList<>()
代码如下:
/** * @author linghu * @date 2023/12/19 11:38 */ public class ListTest { public static void main(String[] args) { /** * 解决方案 * 1、List<String> list = new Vector<>(); * 2、List<String> list = new CopyOnWriteArrayList<>(); */ // List<Object> arrayList = new ArrayList<>(); List<String> list = new CopyOnWriteArrayList<>(); for (int i=1;i<=30;i++){ try { new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(list); },String.valueOf(i)).start(); } catch (Exception e) { e.printStackTrace(); } } } }
运行结果:
结论:
CopyOnWriteArrayList
:写入时复制!能解决集合的线程安全问题!
总结
Vector
底层使用的是synchronized
来实现的,所以效率特别低下。CopyOnWriteArrayList
使用的是Lock
锁,效率会更加高效。
Set集合不安全
Set和List同理可得:多线程情况下,普通的Set集合是线程不安全的。
如下代码,我在循环30次中执行线程,创建set集合:
package org.example; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; /** * @author linghu * @date 2023/12/19 14:27 */ public class SetTest { public static void main(String[] args) { /** * 解决方案 */ Set<String> set = new HashSet<>(); for (int i=1;i<=30;i++){ try { new Thread(()->{ set.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(set); },String.valueOf(i)).start(); } catch (Exception e) { e.printStackTrace(); } } } }
运行效果:
List集合不安全的解决方案
解决方案如下:
CopyOnWriteArraySet<>()
HashSet<>()
我们还是用了类似的原理:写入时复制!
代码如下:
package org.example; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; /** * @author linghu * @date 2023/12/19 14:27 */ public class SetTest { public static void main(String[] args) { /** * 解决方案 * 1、Set<String> set = new CopyOnWriteArraySet<>(); */ // Set<String> set = new HashSet<>(); Set<String> set = new CopyOnWriteArraySet<>(); for (int i=1;i<=30;i++){ try { new Thread(()->{ set.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(set); },String.valueOf(i)).start(); } catch (Exception e) { e.printStackTrace(); } } } }
结论:
CopyOnWriteArraySet
:写入时复制!能解决集合的线程安全问题!
总结
HashSet的底层是HashMap
CopyOnWriteArraySet
:写入时复制!能解决集合的线程安全问题!
Callable
Callable的出现主要是为了弥补继承Thread或实现Runnable接口的线程执行完成后,无法获得线程执行后的结果。
Callable其实上手是非常简单容易得!
我们看通过Callable创建线程的方式如下:
class MyThread implements Callable<String>{ //这里的泛型返回类型和call方法的返回类型是一样的 @Override public String call() throws Exception { return "hi,call"; } }
上面,线程 MyThread
通过继承接口Callable
,这里的String其实是泛型。如下的call
方法的类型和这个泛型相同。
如下代码,我们通过接口 Callable
创建了A、B线程执行 call
方法。
package org.example; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * @author linghu * @date 2023/12/19 16:42 */ public class CallableTest { public static void main(String[] args) throws ExecutionException, InterruptedException { //?怎么启动Callable MyThread myThread = new MyThread(); //适配类 FutureTask futureTask = new FutureTask(myThread); new Thread(futureTask,"A").start(); new Thread(futureTask,"B").start();//结果被缓存,效率高,结果只打印一次 //获取Callable的返回结果 String o = (String)futureTask.get(); System.out.println(o); } } class MyThread implements Callable<String>{ //这里的泛型返回类型和call方法的返回类型是一样的 @Override public String call() throws Exception { return "hi,call"; } }
执行结果:
高并发常用-辅助类
在高并发场景中,常用辅助类如下:
CountDownLatch
CyclickBarrier
Semaphore
CountDownLatch
其实CountDownLatch
的本质就是一个减法计数器。
比如我们去游乐园坐激流勇进,有的时候游乐园里人不是那么多,这时,管理员会让你稍等一下,等人坐满了再开船,这样的话可以在一定程度上节约游乐园的成本。座位有多少,就需要等多少人,这就是 CountDownLatch 的核心思想,等到一个设定的数值达到之后,才能出发。
上面这幅图就很好理解了。可以看到,最开始 CountDownLatch
设置的初始值为 3,然后 T0 线程上来就调用 await
方法,它的作用是让这个线程开始等待,等待后面的 T1、T2、T3,它们每一次调用 countDown 方法,3 这个数值就会减 1,也就是从 3 减到 2,从 2 减到 1,从 1 减到 0,一旦减到 0 之后,这个 T0 就相当于达到了自己触发继续运行的条件,于是它就恢复运行了。
我们可以限制6个线程,等待他们都执行完毕,代码如下:
package org.example; import java.util.concurrent.CountDownLatch; /** * @author linghu * @date 2023/12/20 9:44 */ public class CountDownLatchDemo { public static void main(String[] args) throws InterruptedException { //总数是6个线程 CountDownLatch countDownLatch = new CountDownLatch(6); for (int i=1;i<=6;i++){ new Thread(()->{ System.out.println(Thread.currentThread().getName()+"Go out"); countDownLatch.countDown();//数量-1,本质是阻塞 },String.valueOf(i)).start(); } countDownLatch.await();//等待计数器归0,然后往下执行~ System.out.println("Close Door"); } }
总结
- countDown()是减一操作
- await是等待计数器归0操作
《深入探索Java并发编程&从锁到并发工具的深入解析》(下)+https://developer.aliyun.com/article/1625014