JUC并发编程
准备工作
1.新建maven项目juc
导入lombok
<dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.8</version> </dependency> </dependencies>
2.确定jdk8工作环境
1、什么是JUC
源码+官方文档
JUC就是java.util.concurrent下面的类包,专门用于多线程的开发。
java.util 工具包、包、分类
业务:普通的线程代码 Thread
Runnable 没有返回值、效率相比于Callable相对较低!
2、线程和进程
线程、进程,如果不能使用一句话说出来的,技术不扎实
进程是操作系统中的应用程序、是资源分配的基本单位,线程是用来执行具体的任务和功能,是CPU调度和分派的最小单位一个进程往往可以包含多个线程,至少包含一个
1)进程
一个程序,QQ.exe Music.exe 程序的集合;
一个进程往往可以包含多个线程,至少包含一个!
Java默认有几个线程? 2 个 mian、GC
2)线程
开了一个进程 Typora,写字,自动保存(线程负责的)
对于Java而言:Thread、Runable、Callable进行开启线程的。
JAVA真的可以开启线程吗? 开不了的!
public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } private native void start0();
Java是没有权限去开启线程、操作硬件的,这是一个native的一个本地方法,它调用的底层的C++代码。
并发、并行
并发编程:并发(多线程操作同一资源)
CPU 一核,模拟出来多条线程,天下武功,唯快不破,快速交替
并发编程的本质:充分利用CPU的资源!
并行(多个人一起行走)
CPU多核,多个线程可以同时执行。 我们可以使用线程池!
获取cpu的核数
package com.kuang.demo01; public class Test1 { public static void main(String[] args) { //获取cpu的核数 System.out.println(Runtime.getRuntime().availableProcessors()); } }
线程有几个状态
public enum State { //新生 NEW, //运行 RUNNABLE, //阻塞 BLOCKED, //等待 WAITING, //超时等待 TIMED_WAITING, //终止 TERMINATED; }
wait/sleep区别
1、来自不同的类
wait => Object
sleep => Thread
一般情况企业中使用休眠是:
TimeUnit.DAYS.sleep(1); //休眠1天 TimeUnit.SECONDS.sleep(1); //休眠1s
2、关于锁的释放
wait 会释放锁;
sleep睡觉了,不会释放锁;
3、使用的范围是不同的
wait 必须在同步代码块中;
sleep 可以在任何地方睡;
4、是否需要捕获异常
wait是不需要捕获异常;
sleep必须要捕获异常;
3、Lock锁(重点)
传统 Synchronized
package com.kuang.demo01; //基本的卖票例子 /** * 真正的多线程开发,公司中的开发 * 线程就是一个资源类,没有任何附属的操作 */ public class SaleTicketDemo01 { public static void main(String[] args) { //并发,多线程操作同一个资源类,把资源类丢入线程 Ticket ticket=new Ticket(); //@FunctionalInterface 函数式接口,jdk1.8 Lambdab表达式(参数)->{代码} new Thread(()->{ for(int i=1;i<40;i++){ ticket.sale(); } },"A").start(); new Thread(()->{ for(int i=1;i<40;i++){ ticket.sale(); } },"B").start(); new Thread(()->{ for(int i=1;i<40;i++){ ticket.sale(); } },"C").start(); } } //资源类 OOP class Ticket{ //属性、方法 private int number=50; //卖票的方式 public void sale() { if (number>0) { System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余:"+number); } } }
修改
//卖票的方式 public synchronized void sale() { if (number>0) { System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余:"+number); } }
lock
公平锁:十分公平,先来后到,排队.
非公平锁:不公平,可以插队(默认)
默认是非公平锁,是为了公平,比如一个线程要3s,另一个线程要3h,难道一定要让3h的锁先来就先执行吗?!
package com.kuang.demo01; //基本的卖票例子 import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 真正的多线程开发,公司中的开发 * 线程就是一个资源类,没有任何附属的操作 */ public class SaleTicketDemo02 { public static void main(String[] args) { //并发,多线程操作同一个资源类,把资源类丢入线程 Ticket2 ticket=new Ticket2(); //@FunctionalInterface 函数式接口,jdk1.8 Lambdab表达式(参数)->{代码} new Thread(()->{ for(int i=1;i<40;i++) ticket.sale(); },"A").start(); new Thread(()->{ for(int i=1;i<40;i++) ticket.sale(); },"B").start(); new Thread(()->{ for(int i=1;i<40;i++) ticket.sale(); },"C").start(); } } // Lock class Ticket2{ //属性、方法 private int number=50; Lock lock=new ReentrantLock(); //卖票的方式 public synchronized void sale() { lock.lock();//加锁 try { //业务代码 if (number>0) { System.out.println(Thread.currentThread().getName()+"卖出了"+(number--)+"票,剩余:"+number); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock();//解锁 } } }
Synchronized 与Lock 的区别
1、Synchronized 内置的Java关键字,Lock是一个Java类
2、Synchronized 无法判断获取锁的状态,Lock可以判断
3、Synchronized 会自动释放锁,lock必须要手动加锁和手动释放锁!可能会遇到死锁
4、Synchronized 线程1(获得锁->阻塞)、线程2(等待);lock就不一定会一直等待下去,lock会有一个trylock去尝试获取锁,不会造成长久的等待。
5、Synchronized 是可重入锁,不可以中断的,非公平的;Lock,可重入的,可以判断锁,可以自己设置公平锁和非公平锁;
6、Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码;
锁是什么,如何判断锁的是谁
4、生产者和消费者问题
面试高频: 单例模式, 八大排序,生产者消费者,死锁
生产者和消费者问题 Synchronzied 版
package com.kuang.pc; /** * 线程之间的通信问题:生产者和消费者问题! 等待唤醒 通知唤醒 * 线程交替执行 A B 操作同一个变量 num=0 * A num+1 * B num-1 */ public class A { public static void main(String[] args) { Data data=new Data(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"A").start(); 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){//0 //等待 this.wait(); } number++; System.out.println(Thread.currentThread().getName()+"=>"+number); //通知其他线程,我+1完毕了 this.notify(); } //-1 public synchronized void decrement() throws InterruptedException { if(number==0){//1 //等待 this.wait(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); //通知其他线程,我+1完毕了 this.notify(); } }
问题存在,A B C D 4个线程 虚假唤醒
解决方式 ,if 改为while即可,防止虚假唤醒
结论:就是用if判断的话,唤醒后线程会从wait之后的代码开始运行,但是不会重新判断if条件,直接继续运行if代码块之后的代码,而如果使用while的话,也会从wait之后的代码运行,但是唤醒后会重新判断循环条件,如果不成立再执行while代码块之后的代码块,成立的话继续wait。
这也就是为什么用while而不用if的原因了,因为线程被唤醒后,执行开始的地方是wait之后
自己的见解
比如超市要抓小偷,
小偷偷了100个东西,
if就是你让小偷只走一次防盗检测门,它报警了,小偷成功的偷走了99个东西
while就是让小偷再走一次,直到不报警
package com.kuang.pc; /** * 线程之间的通信问题:生产者和消费者问题! 等待唤醒 通知唤醒 * 线程交替执行 A B 操作同一个变量 num=0 * A num+1 * B num-1 */ public class A { public static void main(String[] args) { Data data=new Data(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"A").start(); 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.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"C").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"D").start(); } } //等待 业务 通知 class Data{//数字 资源类 private int number=0; //+1 public synchronized void increment() throws InterruptedException { while(number!=0){//0 //等待 this.wait(); } number++; System.out.println(Thread.currentThread().getName()+"=>"+number); //通知其他线程,我+1完毕了 this.notify(); } //-1 public synchronized void decrement() throws InterruptedException { while(number==0){//1 //等待 this.wait(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); //通知其他线程,我+1完毕了 this.notify(); } }
JUC版的生产者消费者:Clock 版
代码实现
package com.kuang.pc; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 线程之间的通信问题:生产者和消费者问题! 等待唤醒 通知唤醒 * 线程交替执行 A B 操作同一个变量 num=0 * A num+1 * B num-1 */ public class B { public static void main(String[] args) { Data2 data=new Data2(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"A").start(); 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.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"C").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"D").start(); } } //等待 业务 通知 class Data2{//数字 资源类 private int number=0; Lock lock=new ReentrantLock(); Condition condition = lock.newCondition(); // condition.await();//等待 // condition.signalAll();//唤醒全部 //+1 public void increment() throws InterruptedException { lock.lock(); try { //业务代码 while(number!=0){//0 //等待 condition.await(); } number++; System.out.println(Thread.currentThread().getName()+"=>"+number); //通知其他线程,我+1完毕了 condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } //-1 public void decrement() throws InterruptedException { lock.lock(); try { //业务代码 while(number==0){//1 //等待 condition.await(); } number--; System.out.println(Thread.currentThread().getName()+"=>"+number); //通知其他线程,我+1完毕了 condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
任何一个新技术,绝对不仅仅只是覆盖了原来的技术,优势和补充!
Condition 精准的通知和唤醒的线程!
如果我们要指定通知的下一个进行顺序怎么办呢? 我们可以使用Condition来指定通知进程~
代码测试:
package com.kuang.pc; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Description: * A 执行完 调用B * B 执行完 调用C * C 执行完 调用A * * @author CSDN@日星月云 * @date 11:31:18 2022年7月13日 **/ public class C { public static void main(String[] args) { Data3 data=new Data3(); new Thread(()->{ for (int i = 0; i < 10; i++) { data.printA(); } },"A").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { data.printB(); } },"B").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { data.printC(); } },"C").start(); } } class Data3{// 资源类 lock private Lock lock=new ReentrantLock(); private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); private Condition condition3 = lock.newCondition(); private int number=1;// 1A 2B 3C public void printA() { lock.lock(); try { //业务,判断->执行->通知 while (number!=1){ //等待 condition1.await(); } System.out.println(Thread.currentThread().getName()+"=>AAAAAA"); //唤醒,唤醒指定的人,B number=2; condition2.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printB() { lock.lock(); try { //业务,判断->执行->通知 while (number!=2){ //等待 condition2.await(); } System.out.println(Thread.currentThread().getName()+"=>BBBBBB"); //唤醒,唤醒指定的人,C number=3; condition3.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printC() { lock.lock(); try { //业务,判断->执行->通知 while (number!=3){ //等待 condition3.await(); } System.out.println(Thread.currentThread().getName()+"=>CCCCCC"); //唤醒,唤醒指定的人,A number=1; condition1.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
信号量机制 Semaphore 实现
经典 生产者-消费者线程【操作系统】
5、8锁现象
如何判断锁的是谁!锁到底锁的是谁?
锁会锁住:对象、Class
深刻理解我们的锁
1、标准情况下,两个线程先打印 发短信 还是 打电话 ?
package com.kuang.lock8; import java.util.concurrent.TimeUnit; /** * 8锁就,就是关于锁的8个问题 * 1、标准情况下,两个线程先打印 发短信 还是 打电话 ? 1/发短信 2/打电话 */ public class Test1 { public static void main(String[] args) { Phone phone = new Phone(); new Thread(() -> { phone.sendSMs(); }).start(); //捕获 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { phone.call(); }).start(); } } class Phone { public synchronized void sendSMs() { System.out.println("发短信"); } public synchronized void call() { System.out.println("打电话"); } }
结果:
1/发短信 2/打电话
为什么? 如果你认为是顺序在前? 这个答案是错误的!
2、sendMs延迟4秒,两个线程先打印 发短信 还是 打电话 ?
package com.kuang.lock8; import java.util.concurrent.TimeUnit; /** * 8锁就,就是关于锁的8个问题 * 1、标准情况下,两个线程先打印 发短信 还是 打电话 ? 1/发短信 2/打电话 * 2、sendSMs延迟4秒,两个线程先打印 发短信 还是 打电话 ? (4s) 1/发短信 2/打电话 */ public class Test1 { public static void main(String[] args) { Phone phone = new Phone(); //锁的存在 new Thread(() -> { phone.sendSMs(); }).start(); //捕获 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { phone.call(); }).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("打电话"); } }
结果:
(4s) 1/发短信 2/打电话
原因:并不是顺序执行,而是synchronized 锁住的对象是方法的调用!对于两个方法用的是同一个锁,谁先拿到谁先执行,另外一个等待
3、增加一个普通方法后!两个线程先打印 发短信 还是 hello ?
package com.kuang.lock8; import java.util.concurrent.TimeUnit; /** * 3、增加一个普通方法后!两个线程先打印 发短信 还是 hello ? (1s) 1/hello (4s) 2/发短信 */ public class Test2 { public static void main(String[] args) { Phone2 phone = new Phone2(); //锁的存在 new Thread(() -> { phone.sendSMs(); }).start(); //捕获 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { phone.hello(); }).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"); } }
结果:
(1s) 1/hello (4s) 2/发短信
4、两个对象,两个同步方法,先打印 发短信 还是 打电话 ?
package com.kuang.lock8; import java.util.concurrent.TimeUnit; /** * 3、增加一个普通方法后!两个线程先打印 发短信 还是 hello ? (1s) 1/hello (4s) 2/发短信 * 4、两个对象,两个同步方法,先打印 发短信 还是 打电话 ? (4s) 1/发短信 2/打电话 */ public class Test2 { public static void main(String[] args) { //两个对象,两个调用者,两个锁 Phone2 phone1 = new Phone2(); Phone2 phone2 = new Phone2(); //锁的存在 new Thread(() -> { phone1.sendSMs(); }).start(); //捕获 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { phone2.call(); }).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"); } }
结果:
(4s) 1/发短信 2/打电话
5、增加两个静态的同步方法,只有一个对象,先打印 发短信 还是 打电话 ?
package com.kuang.lock8; import java.util.concurrent.TimeUnit; /** * 5、增加两个静态的同步方法,只有一个对象,先打印 发短信 还是 打电话 ? (4s) 1/发短信 2/打电话 */ public class Test3 { public static void main(String[] args) { Phone3 phone = new Phone3(); //锁的存在 new Thread(() -> { phone.sendSMs(); }).start(); //捕获 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { phone.call(); }).start(); } } //Phone唯一放入一个class对象 class Phone3 { //synchronized 锁的对象是方法的调用者! //static 静态方法 //类一加载就有了!锁的是 Class 模板 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("打电话"); } }
结果:
(4s) 1/发短信 2/打电话
6、两个对象!增加两个静态的同步方法,只有一个对象,先打印 发短信 还是 打电话 ?
package com.kuang.lock8; import java.util.concurrent.TimeUnit; /** * 5、增加两个静态的同步方法,只有一个对象,先打印 发短信 还是 打电话 ? (4s) 1/发短信 2/打电话 * 6、两个对象!增加两个静态的同步方法,只有一个对象,先打印 发短信 还是 打电话 ? (4s) 1/发短信 2/打电话 */ public class Test3 { public static void main(String[] args) { //两个对象的Class类模板只有一个,static,锁的是Class Phone3 phone1 = new Phone3(); Phone3 phone2 = new Phone3(); //锁的存在 new Thread(() -> { phone1.sendSMs(); }).start(); //捕获 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { phone2.call(); }).start(); } } //Phone唯一放入一个class对象 class Phone3 { //synchronized 锁的对象是方法的调用者! //static 静态方法 //类一加载就有了!锁的是 Class 模板 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("打电话"); } }
结果:
(4s) 1/发短信 2/打电话
7、1个静态同步方法,1个普通同步方法,一个对象,先打印 发短信 还是 打电话 ?
package com.kuang.lock8; import java.util.concurrent.TimeUnit; /** * 7、1个静态同步方法,1个普通同步方法,一个对象,先打印 发短信 还是 打电话 ? (1s)1/打电话(4s) 2/发短信 */ public class Test4 { public static void main(String[] args) { Phone4 phone = new Phone4(); //锁的存在 new Thread(() -> { phone.sendSMs(); }).start(); //捕获 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { phone.call(); }).start(); } } //Phone唯一放入一个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("打电话"); } }
结果:
(1s)1/打电话(4s) 2/发短信
8、1个静态同步方法,1个普通同步方法,两个对象,先打印 发短信 还是 打电话 ?
package com.kuang.lock8; import java.util.concurrent.TimeUnit; /** * 7、1个静态同步方法,1个普通同步方法,一个对象,先打印 发短信 还是 打电话 ? (1s)1/打电话(4s) 2/发短信 * 8、1个静态同步方法,1个普通同步方法,两个对象,先打印 发短信 还是 打电话 ? (1s)1/打电话(4s) 2/发短信 */ public class Test4 { public static void main(String[] args) { Phone4 phone1 = new Phone4(); Phone4 phone2 = new Phone4(); //锁的存在 new Thread(() -> { phone1.sendSMs(); }).start(); //捕获 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { phone2.call(); }).start(); } } //Phone唯一放入一个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("打电话"); } }
结果:
(1s)1/打电话(4s) 2/发短信
小结
new 出来的 this 是具体的一个对象
static Class 是唯一的一个模板
6、集合类不安全
list 不安全
package com.kuang.unsafe; import java.util.ArrayList; import java.util.List; import java.util.UUID; //java.util.ConcurrentModificationException 并发修改异常 public class ListTest { public static void main(String[] args) { List<String> list=new ArrayList<>(); for (int i = 1; i <= 10; i++) { new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(list); },String.valueOf(i)).start(); } } }
解决方案:
package com.kuang.unsafe; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; //java.util.ConcurrentModificationException 并发修改异常 public class ListTest { public static void main(String[] args) { //并发下 ArrayList 不安全的 /** * 解决方案: * 1、 List<String> list=new Vector<>(); * 2、 List<String> list= Collections.synchronizedList(new ArrayList<>()); * 3、 List<String> list= new CopyOnWriteArrayList<>(); */ //CopyOnWrite写入是复制 COW 计算机程序设计领域的一种优化策略; //多个线程调用的时候,list,读取的时候,固定的,写入(覆盖) //在写入的时候,避免覆盖,造成数据问题 //读写分离 //CopyOnWriteArrayList 比 Vector 牛逼在哪里 List<String> list= new CopyOnWriteArrayList<>(); for (int i = 1; i <= 10; i++) { new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(list); },String.valueOf(i)).start(); } } }