线程
注意:增删改频繁的时候要加锁,读取文件下不加锁
synchornized后面只能加对象
一概念.
线程 称轻量级进程,程序中的一个顺序控制流程,同时也是CPU的基本调度单位.进程由多个线程组成,彼此完成不同的工作,交替执行,称为多线程,
JAVA虚拟机是一个进程,默认包含主线程(Main),可通过代码创建多个独立线程,与Main并发执行.
二组成.
基本的组成部分:
一.CPU时间片:操作系统会为每个线程分配执行时间
二.运行数据:
- 堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象
- 栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈
- 三.线程的逻辑代码
三.创建线程
俩种方式:
1.继承Thread类:直接变成线程类
2.实现Runnable接口:只是成为线程类的一部分功能,但不是继承类(一个task),传入Thread对象并执行
俩种方式的区别:
两种实现多线程的方式对比:
2. java是单继承,一旦一个类继承了Thread类之后,就不能再去继承第二个类,所以第一种方式显得不够灵活,而第二种就可以
3. 实现Runnable接口的方式,用Runnable对象作为参数来创建新的线程对象才能够让该线程对象启动后起到多线程的作用,可以实现很好的资源共享,但是代码会显得稍微繁琐。
4. 第二种实现Runnable接口的方式,特别强调,Runnable对象自身不具备多线程的能力,只有该对象放入Thread构造器中才可以。没有start()方法
1.第一种方式
package ThreadBasicTest;
public class TestThread {
public static void main(String[] args) {
//3.创建子类对象
MyThread myThread = new MyThread();
MyThread2 myThread2 = new MyThread2();
//4.调用start()方法
myThread.run();//直接调用方法,没有调用线程
myThread.start();//由Jvm调用run()方法
for (int i = 0; i <5 ; i++) {
System.out.println("Main"+i);
}
myThread2.run();
for (int i = 0; i <5 ; i++) {
System.out.println("ma"+i);
}
}
}
//1.继承Thread类
class MyThread extends Thread{
//线程的任务
@Override
//2.覆盖run()方法
public void run() {
for (int i = 0; i <5 ; i++) {
System.out.println("Thread"+i);
}
}
}
class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i <5 ; i++) {
System.out.println("NyTread"+i);
}
}
}
/*
Main0
Main1
Main2
Main3
Main4
NyTread0
NyTread1
NyTread2
NyTread3
NyTread4
ma0
ma1
ma2
ma3
ma4
Thread0
Thread1
Thread2
Thread3
Thread4
*/
2.第二种方式:
package ThreadBasicTest;
public class TestThreaf02 {
public static void main(String[] args) {
//3.创建实现对象
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);//40创建线程对象
thread.start();//5.调用run方法
for (int i = 0; i <10 ; i++) {
System.out.println("Main"+"\t");
}
}
}
//1.实现Runnable接口
class MyRunnable implements Runnable{
//2.覆盖run()方法
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
// System.out.println("MyRunnable:"+i);
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
}
四.线程的状态(基本)
初始状态:
New(); 线程对象被创建即为初始状态,只在堆中开辟内存,与常规对象无异义,没有争取CPU时间片的能力
就绪状态:
**Ready:**调用start()之后,进入就绪状态,等待CPU选中,并分配时间片
运行状态:
Running:获得时间片之后**,进入运行状态**,如果时间片到期,则回到就绪状态
终止状态:
**Terminated:**主线程Main()或独立线程run()结束,进入终止状态,并释放持有的时间片
Jdk5之后,就绪,运行状态同一称为Runable
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K5RXAfPm-1676340621901)(D:\JAVA\JavaStudy\笔记\img\线程的状态.jpg)]
五.线程的常见方法
package ThreadMethod;
public class Method01 {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
//通知完myThread后,main后休眠2秒
Thread.sleep(2000);
for (int i = 0; i <10 ; i++) {
System.out.println(Thread.currentThread().getName()+"-"+i);
}
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
//线程类
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
//获得当前线程的线程名称
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
if (i %2==0) {
System.out.println("线程2除以偶数休眠2秒");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+i);
}
}
}
}
/*
Thread-0-0
Thread-0-1
Thread-0-2
Thread-0-3
Thread-0-4
Thread-0-5
Thread-0-6
Thread-0-7
Thread-0-8
Thread-0-9
main-0
main-1
main-2
main-3
main-4
main-5
main-6
main-7
main-8
main-9
线程2除以偶数休眠2秒
Thread-10
线程2除以偶数休眠2秒
Thread-12
线程2除以偶数休眠2秒
Thread-14
线程2除以偶数休眠2秒
Thread-16
线程2除以偶数休眠2秒
Thread-18
线程2除以偶数休眠2秒
Thread-110
线程2除以偶数休眠2秒
Thread-112
线程2除以偶数休眠2秒
Thread-114
线程2除以偶数休眠2秒
Thread-116
线程2除以偶数休眠2秒
Thread-118
package ThreadMethod;
public class TestYield {
public static void main(String[] args) {
Thread t1 =new Thread(new Task());
t1.start();
for (int i = 0; i <10 ; i++) {
System.out.println(Thread.currentThread().getName()+"_-"+i);
if (i %2== 0) {
System.out.println("main主动放弃");
Thread.yield();//主动放弃这次时间片,进入下一次时间片
}
}
}
}
class Task implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
}
package ThreadMethod;
public class TestJoin {
public static void main(String[] args) throws InterruptedException {
Thread thread1 =new Thread(new Task2());//匿名方法
Thread thread2 =new Thread(new Task2());//匿名方法
thread1.start();
thread2.start();
for (int i = 0; i <=50; i++) {
System.out.println(Thread.currentThread().getName()+"-"+i);
if (i == 20) {
System.out.println("main执行到20了,执行t1");
thread1.join();//将t1加入到Main线程中,等待t1执行结束后,main再进行竞争片
}
}
}
}
class Task2 implements Runnable{
@Override
public void run() {
for (int i = 0; i <=50; i++) {
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
}
/*
main-0
main-1
Thread-0-0
Thread-0-1
Thread-0-2
Thread-0-3
Thread-0-4
Thread-0-5
Thread-0-6
Thread-0-7
Thread-0-8
Thread-0-9
Thread-0-10
Thread-1-0
Thread-1-1
Thread-1-2
Thread-1-3
Thread-1-4
Thread-1-5
Thread-1-6
Thread-1-7
Thread-1-8
Thread-1-9
Thread-1-10
Thread-1-11
Thread-1-12
Thread-1-13
Thread-0-11
Thread-0-12
Thread-0-13
main-2
Thread-0-14
Thread-1-14
Thread-1-15
Thread-1-16
Thread-1-17
Thread-1-18
Thread-0-15
main-3
main-4
main-5
main-6
Thread-0-16
Thread-0-17
Thread-0-18
Thread-0-19
Thread-1-19
Thread-1-20
Thread-0-20
Thread-0-21
main-7
Thread-0-22
Thread-0-23
Thread-0-24
Thread-0-25
Thread-0-26
Thread-0-27
Thread-0-28
Thread-0-29
Thread-0-30
Thread-0-31
Thread-0-32
Thread-0-33
Thread-0-34
Thread-0-35
Thread-0-36
Thread-0-37
Thread-0-38
Thread-0-39
Thread-0-40
Thread-0-41
Thread-0-42
Thread-0-43
Thread-0-44
Thread-0-45
Thread-0-46
Thread-0-47
Thread-0-48
Thread-0-49
Thread-0-50
Thread-1-21
Thread-1-22
Thread-1-23
main-8
Thread-1-24
main-9
Thread-1-25
main-10
main-11
main-12
main-13
main-14
Thread-1-26
Thread-1-27
Thread-1-28
Thread-1-29
main-15
main-16
main-17
Thread-1-30
main-18
main-19
Thread-1-31
main-20
main执行到20了,执行t1
Thread-1-32
main-21
main-22
main-23
main-24
Thread-1-33
main-25
main-26
main-27
Thread-1-34
Thread-1-35
Thread-1-36
Thread-1-37
Thread-1-38
main-28
Thread-1-39
main-29
Thread-1-40
Thread-1-41
main-30
Thread-1-42
main-31
Thread-1-43
Thread-1-44
Thread-1-45
Thread-1-46
Thread-1-47
Thread-1-48
Thread-1-49
main-32
Thread-1-50
main-33
main-34
main-35
main-36
main-37
main-38
main-39
main-40
main-41
main-42
main-43
main-44
main-45
main-46
main-47
main-48
main-49
main-50
Process finished with exit code 0
六.线程安全问题
-
概念:
-
当多线程并发访问临界资源时,如果破坏原子操作,可能造成数据不一致
-
临界资源:**共享资源(同一对象),**一次允许一个线程使用,才可保证其正确性
-
原子操作:不可分割的多步操作,视为一个整体,其顺序不可打乱
package XianChengAnQuan;
public class TestSynchronized {
public static void main(String[] args) {
Account account = new Account("6002","123456",200);
//俩个线程对象共享同一银行卡资源对象
Thread husband = new Thread(new HusBand(account),"丈夫");
Thread wife = new Thread(new Wife(account),"妻子");
husband.start();
wife.start();
}
}
class HusBand implements Runnable{
Account acc;
public HusBand(Account acc) {
this.acc = acc;
}
//线程任务:取款
public void run(){
this.acc.withdrawal("6002","123456",120);
}
}
class Wife implements Runnable{
Account acc;
public Wife(Account acc) {
this.acc = acc;
}
//线程任务:取款
public void run(){
this.acc.withdrawal("6002","123456",120);
}
}
//银行账号,银行卡
class Account{
String cardNo;
String password;
double balance;
public Account() {
}
public Account(String cardNo, String password, double balance) {
this.cardNo = cardNo;
this.password = password;
this.balance = balance;
}
//取款:原子操作,不可缺少或者打乱
public void withdrawal(String no,String pwd,double money){
System.out.println(Thread.currentThread().getName()+"正在读卡");
if (no.equals(this.cardNo)&&pwd.equals(this.password)) {
System.out.println(Thread.currentThread().getName()+"验证成功");
if (money <= this.balance) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = this.balance - money;
System.out.println(Thread.currentThread().getName()+"取钱成功! 当前余额为" + this.balance);
} else {
System.out.println(Thread.currentThread().getName()+"当前卡内余额不足");
}
}else {
System.out.println("卡号或者密码错误");
}
}
}
/*
丈夫正在读卡
妻子正在读卡
妻子验证成功
丈夫验证成功
妻子取钱成功! 当前余额为80.0
丈夫取钱成功! 当前余额为80.0
七.解决线程不安全的方法:
一:同步方式(1)
-
同步代码块:(为方法中的局部代码(原子操作))加锁
-
synchronized(临界资源对象){//对临界资源对象加锁
-
//代码(原子操作
-
)
-
注意:每个对象都有一个互斥锁标记,用来分配给线程
-
只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块
-
线程退出同步代码块,才会释放相应的互斥锁标记
package XianChengAnQuan;
public class TestSynchronized {
public static void main(String[] args) {
Account account = new Account("6002","123456",200);
//俩个线程对象共享同一银行卡资源对象
//临界资源对象只有一把锁
Thread husband = new Thread(new HusBand(account),"丈夫");
Thread wife = new Thread(new Wife(account),"妻子");
husband.start();
wife.start();
}
}
class HusBand implements Runnable{
Account acc;
public HusBand(Account acc) {
this.acc = acc;
}
//线程任务:取款
public void run(){
synchronized(acc){//对临界资源对象加锁
this.acc.withdrawal("6002","123456",120);//原子操作
}//还锁
}
}
class Wife implements Runnable{
Account acc;
public Wife(Account acc) {
this.acc = acc;
}
//线程任务:取款
public void run(){
synchronized(acc){//对临界资源对象加锁
this.acc.withdrawal("6002","123456",120);//原子操作
}
}
}
//银行账号,银行卡
class Account{
String cardNo;
String password;
double balance;
public Account() {
}
public Account(String cardNo, String password, double balance) {
this.cardNo = cardNo;
this.password = password;
this.balance = balance;
}
//取款:原子操作,不可缺少或者打乱
public void withdrawal(String no,String pwd,double money){
//等待--阻塞状态
//synchronized(this){}
System.out.println(Thread.currentThread().getName()+"正在读卡");
if (no.equals(this.cardNo)&&pwd.equals(this.password)) {
System.out.println(Thread.currentThread().getName()+"验证成功");
if (money <= this.balance) {
try {
Thread.sleep(100);//ATm数钱
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = this.balance - money;
System.out.println(Thread.currentThread().getName()+"取钱成功! 当前余额为" + this.balance);
} else {
System.out.println(Thread.currentThread().getName()+"当前卡内余额不足");
}
}else {
System.out.println("卡号或者密码错误");
}
}
}
/*
丈夫正在读卡
丈夫验证成功
丈夫取钱成功! 当前余额为80.0
妻子正在读卡
妻子验证成功
妻子当前卡内余额不足
二.同步方法(2):(为方法中所有的代码(原子性)加锁 )
- 同步方法
- synchronized 返回值类型 方法名称(形参列表0){对当前的this加锁
- /代码(原子操作)
- }
注意:只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中,
线程退出同步方法时,会释放相应的互斥锁标记
package XianChengAnQuanTwo;
public class TestSynchronized {
public static void main(String[] args) {
Account account = new Account("6002","123456",200);
//俩个线程对象共享同一银行卡资源对象
//临界资源对象只有一把锁
Thread husband = new Thread(new HusBand(account),"丈夫");
Thread wife = new Thread(new Wife(account),"妻子");
husband.start();
wife.start();
}
}
class HusBand implements Runnable{
Account acc;
public HusBand(Account acc) {
this.acc = acc;
}
//线程任务:取款
public void run(){
synchronized(acc){//对临界资源对象加锁
this.acc.withdrawal("6002","123456",120);//原子操作
}//还锁
}
}
class Wife implements Runnable{
Account acc;
public Wife(Account acc) {
this.acc = acc;
}
//线程任务:取款
public void run(){
synchronized(acc){//对临界资源对象加锁
this.acc.withdrawal("6002","123456",120);//原子操作
}
}
}
//银行账号,银行卡
class Account{
String cardNo;
String password;
double balance;
public Account() {
}
public Account(String cardNo, String password, double balance) {
this.cardNo = cardNo;
this.password = password;
this.balance = balance;
}
//取款:原子操作,不可缺少或者打乱
//同步方法的代码跟同步代码块基本一致,只是把synchronized放在方法前而已
public synchronized void withdrawal(String no,String pwd,double money){
//等待 -阻塞状态
// synchronized ((this){
System.out.println(Thread.currentThread().getName()+"正在读卡");
if (no.equals(this.cardNo)&&pwd.equals(this.password)) {
System.out.println(Thread.currentThread().getName()+"验证成功");
if (money <= this.balance) {
try {
Thread.sleep(100);//ATm数钱
} catch (InterruptedException e) {
e.printStackTrace();
}
this.balance = this.balance - money;
System.out.println(Thread.currentThread().getName()+"取钱成功! 当前余额为" + this.balance);
} else {
System.out.println(Thread.currentThread().getName()+"当前卡内余额不足");
}
}else {
System.out.println("卡号或者密码错误");
}
}
}
八.线程(锁)的同步规则
-
注意
1.只有在调用包含同步代码块的方法,或者同步方法时,才需要对象的锁标记
2.如调用不包含同步代码块的方法,或普通方法是,则不需要锁标记,直接调用
已知jdk中线程安全的类:
1.StringBuffer
2.Vector
3.Hashtable
以上类中的公开方法均为synchronized修饰的同步方法
九,经典问题
一.死锁:
1.当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程
拥有B对象锁标记,并等待A对象锁标记时,产生死锁
2.一个线程可以同时拥有多个对象的锁标记,当线程堵塞时,不会释放已经拥有的锁标记,由此可造成死锁.
package Synchronized;
public class DeadLockTest {
public static void main(String[] args) {
Left left = new Left();
Right right = new Right();
Thread boy = new Thread(new Boy(left,right));
Thread girl =new Thread(new Girl(left,right));
boy.start();
girl.start();
}
}
class Left{
String name="左边";
}
class Right{
String name="右边";
}
class Boy implements Runnable{
Left left;
Right right;
public Boy(Left left,Right right){
this.left=left;
this.right=right;
}
@Override
public void run() {
System.out.println("男孩在左边");
synchronized (right){
//男孩让给女孩
try {
right.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("男孩要在右边吃饭");
}
synchronized (left){
System.out.println("男孩要在左边吃饭");
}
}
}
class Girl implements Runnable{
Left left;
Right right;
public Girl(Left left,Right right){
this.left=left;
this.right=right;
}
@Override
public void run() {
System.out.println("女孩要在右边");
synchronized (left){
System.out.println("女孩要在左边吃饭");
}
synchronized (right){
System.out.println("女孩要在右边吃饭");
//女孩吃完饭后,h唤醒等待男孩的线程
right.notify();
}
}
}
男孩在左边
女孩要在右边
女孩要在左边吃饭
女孩要在右边吃饭
男孩要在右边吃饭
男孩要在左边吃饭
二.生产者和消费者:
- 若干个生产者在生成产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并非执行,在俩者之间设置一个能存储多个产品的缓冲区,生产者将生产的产品放入缓冲区中,消费者从缓冲区取走产品进行消费,显然生产者和消费者之间必须保持同步,即不允许消费者到一个空的缓冲区取产品,也不允许生产者向一个满的缓冲区放入产品.(保证消费者消费有商品,没有的话通知生产者来放入商品,应用的是线程通信,当商品存在的是,生产者等待,当商品不存在的话,消费者等待)
十.线程通信
等待
1.public final void wait();
2.public final void wait(long timeout)
3.必须对obj加锁的同步代码块中,在一个线程中,调用obj.wait()时,此线程会释放
其拥有的所有锁标记.同时此线程阻塞在o的等待队列中.释放锁,进入等待队列中
通知:
1.public final void notify();//随机唤醒一个等待的线程,另一个无线等待
2.public final void notifyAll();//唤醒所有等待的线程
3.必须在对obj加锁的同步代码块中,从obj的Waiting中释放一个或全部到线程,对自身没有任何影响.
简单案例
package Synchronized;
public class TestDeadLock {
public static void main(String[] args) {
Object o = new Object();
MyThread myThread1 = new MyThread(o);
MyThread2 myThread2 = new MyThread2(o);
myThread1.start();
myThread2.start();
}
}
// 复杂一个线程持有A对象的锁,需要B对象的锁,另一个持有B,需要A
//简单 一个线程持有A对象的锁,另一个线程也需要A
class MyThread extends Thread{
Object object;
public MyThread(Object object){
this.object=object;
}
public void run(){
synchronized (object) {
System.out.println(Thread.currentThread().getName()+"进入到同步代码块");
//如果Thead -0先拿到锁,然后主动释放锁给其他线程先使用
try {
object.wait();//主动释放当前持有的锁,并进入无限期等待
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"退出了同步代码块");
}
}
}
class MyThread2 extends Thread{
Object object;
public MyThread2(Object object){
this.object=object;
}
public void run(){
synchronized (object) {
System.out.println(Thread.currentThread().getName()+"进入到同步代码块");
object.notify();//在obj这个共享对象的等待队列,唤醒一个正在等待的线程
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"退出了同步代码块");
}
}
}
结果
Thread-0进入到同步代码块
Thread-1进入到同步代码块
Thread-1退出了同步代码块
Thread-0退出了同步代码块
十一.线程池
一.线程池(池代表复用):解决内存溢出,频繁的创建增加虚拟机的回收频率,资源开销,造成程序性能的下降
线程池概念.
- 线程容器,可设定线程分配的数量上限
- 将预先创建的线程对象存入池中,并重用线程池的线程对象
- 避免频繁的创建和销毁(继续放入线程容器,等使用频率降低,再通过机制回收)
线程池的原理.
- 将任务提交给线程池,由线程池分配线程,运行任务,并在当前任务结束后复用线程.
获取线程池
-
常用的线程池接口和类(所在的包java.util.concurrent):
-
Executor:线程池的顶级接口
1.ExecutorService:线程池接口,可通过submit(Runnable task)提交任务代码
-
Executors工厂类:通过此类可以获得一个线程池
1.通过newFixedThreadPool(int nThreads) 获取固定数量的线程池.参数:指定线程池中线程的数量
-
-
通过newCachedThreadPool()获得动态数量的线程池,如果不够则创建新的,没有上限
package ThreadPool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestThreadPool {
public static void main(String[] args) {
//线程池(引用) -->Executors工厂类
ExecutorService es = Executors.newFixedThreadPool(3);//手动限定线程池的里面线程数量
//1.创建任务对象
MyTask task = new MyTask();
//2.将任务提交给线程池,由线程调度,执行
es.submit(task);//Runnable类型的对象
es.submit(task);
es.submit(task);
//不管提交多个任务都是这3个执行
es.submit(task);
}
}
class MyTask implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
/**
pool-1-thread-1:1
pool-1-thread-3:3
pool-1-thread-3:4
pool-1-thread-3:5
pool-1-thread-3:6
pool-1-thread-3:7
pool-1-thread-3:8
pool-1-thread-3:9
pool-1-thread-2:2
pool-1-thread-3:10
pool-1-thread-1:2
pool-1-thread-3:11
pool-1-thread-2:3
pool-1-thread-3:12
pool-1-thread-3:13
pool-1-thread-1:3
pool-1-thread-1:4
pool-1-thread-1:5
pool-1-thread-3:14
pool-1-thread-3:15
pool-1-thread-2:4
pool-1-thread-2:5
pool-1-thread-2:6
pool-1-thread-2:7
pool-1-thread-2:8
pool-1-thread-2:9
pool-1-thread-2:10
pool-1-thread-2:11
pool-1-thread-2:12
pool-1-thread-2:13
pool-1-thread-2:14
pool-1-thread-2:15
pool-1-thread-2:16
pool-1-thread-2:17
pool-1-thread-2:18
pool-1-thread-2:19
pool-1-thread-2:20
pool-1-thread-2:21
pool-1-thread-2:22
pool-1-thread-2:23
pool-1-thread-2:24
pool-1-thread-2:25
pool-1-thread-2:26
pool-1-thread-2:27
pool-1-thread-2:28
pool-1-thread-3:16
pool-1-thread-1:6
pool-1-thread-1:7
pool-1-thread-1:8
pool-1-thread-1:9
pool-1-thread-1:10
pool-1-thread-1:11
pool-1-thread-1:12
pool-1-thread-1:13
pool-1-thread-1:14
pool-1-thread-1:15
pool-1-thread-1:16
pool-1-thread-1:17
pool-1-thread-3:17
pool-1-thread-3:18
pool-1-thread-3:19
pool-1-thread-3:20
pool-1-thread-2:29
pool-1-thread-3:21
pool-1-thread-3:22
pool-1-thread-3:23
pool-1-thread-3:24
pool-1-thread-3:25
pool-1-thread-3:26
pool-1-thread-1:18
pool-1-thread-3:27
pool-1-thread-2:30
pool-1-thread-3:28
pool-1-thread-3:29
pool-1-thread-3:30
pool-1-thread-3:31
pool-1-thread-3:32
pool-1-thread-1:19
pool-1-thread-1:20
pool-1-thread-3:33
pool-1-thread-2:31
pool-1-thread-2:32
pool-1-thread-2:33
pool-1-thread-2:34
pool-1-thread-2:35
pool-1-thread-3:34
pool-1-thread-3:35
pool-1-thread-3:36
pool-1-thread-3:37
pool-1-thread-3:38
pool-1-thread-1:21
pool-1-thread-1:22
pool-1-thread-3:39
pool-1-thread-3:40
pool-1-thread-2:36
pool-1-thread-3:41
pool-1-thread-1:23
pool-1-thread-3:42
pool-1-thread-2:37
pool-1-thread-3:43
pool-1-thread-3:44
pool-1-thread-3:45
pool-1-thread-3:46
pool-1-thread-3:47
pool-1-thread-3:48
pool-1-thread-3:49
pool-1-thread-1:24
pool-1-thread-1:25
pool-1-thread-1:26
pool-1-thread-1:27
pool-1-thread-1:28
pool-1-thread-1:29
pool-1-thread-1:30
pool-1-thread-1:31
pool-1-thread-1:32
pool-1-thread-1:33
pool-1-thread-1:34
pool-1-thread-1:35
pool-1-thread-1:36
pool-1-thread-2:38
pool-1-thread-2:39
pool-1-thread-1:37
pool-1-thread-3:0
pool-1-thread-3:1
pool-1-thread-3:2
pool-1-thread-3:3
pool-1-thread-1:38
pool-1-thread-2:40
pool-1-thread-2:41
pool-1-thread-2:42
pool-1-thread-2:43
pool-1-thread-2:44
pool-1-thread-2:45
pool-1-thread-2:46
pool-1-thread-2:47
pool-1-thread-2:48
pool-1-thread-2:49
pool-1-thread-1:39
pool-1-thread-1:40
pool-1-thread-1:41
pool-1-thread-1:42
pool-1-thread-1:43
pool-1-thread-3:4
pool-1-thread-3:5
pool-1-thread-3:6
pool-1-thread-3:7
pool-1-thread-3:8
pool-1-thread-3:9
pool-1-thread-3:10
pool-1-thread-3:11
pool-1-thread-3:12
pool-1-thread-3:13
pool-1-thread-3:14
pool-1-thread-3:15
pool-1-thread-3:16
pool-1-thread-3:17
pool-1-thread-3:18
pool-1-thread-3:19
pool-1-thread-3:20
pool-1-thread-3:21
pool-1-thread-3:22
pool-1-thread-3:23
pool-1-thread-3:24
pool-1-thread-3:25
pool-1-thread-3:26
pool-1-thread-3:27
pool-1-thread-3:28
pool-1-thread-3:29
pool-1-thread-3:30
pool-1-thread-3:31
pool-1-thread-3:32
pool-1-thread-3:33
pool-1-thread-3:34
pool-1-thread-3:35
pool-1-thread-3:36
pool-1-thread-3:37
pool-1-thread-3:38
pool-1-thread-3:39
pool-1-thread-3:40
pool-1-thread-3:41
pool-1-thread-3:42
pool-1-thread-1:44
pool-1-thread-3:43
pool-1-thread-1:45
pool-1-thread-1:46
pool-1-thread-1:47
pool-1-thread-1:48
pool-1-thread-1:49
pool-1-thread-3:44
pool-1-thread-3:45
pool-1-thread-3:46
pool-1-thread-3:47
pool-1-thread-3:48
pool-1-thread-3:49
package ThreadPool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestThreadPoolTest {
public static void main(String[] args) {
ExecutorService es = Executors.newCachedThreadPool();//动态数量的线程池
Task task = new Task();
//
es.submit(task);
es.submit(task);
es.submit(task);
es.submit(task);
es.submit(task);
es.submit(task);
}
}
class Task implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
/**
pool-1-thread-2:0
pool-1-thread-1:0
pool-1-thread-1:1
pool-1-thread-5:0
pool-1-thread-4:0
pool-1-thread-4:1
pool-1-thread-3:0
pool-1-thread-2:1
pool-1-thread-3:1
pool-1-thread-6:0
pool-1-thread-6:1
pool-1-thread-6:2
pool-1-thread-4:2
pool-1-thread-5:1
pool-1-thread-1:2
pool-1-thread-5:2
pool-1-thread-4:3
pool-1-thread-4:4
pool-1-thread-4:5
pool-1-thread-4:6
pool-1-thread-4:7
pool-1-thread-6:3
pool-1-thread-3:2
pool-1-thread-3:3
pool-1-thread-3:4
pool-1-thread-3:5
pool-1-thread-3:6
pool-1-thread-3:7
pool-1-thread-3:8
pool-1-thread-3:9
pool-1-thread-3:10
pool-1-thread-3:11
pool-1-thread-3:12
pool-1-thread-2:2
pool-1-thread-3:13
pool-1-thread-6:4
pool-1-thread-6:5
pool-1-thread-6:6
pool-1-thread-6:7
pool-1-thread-6:8
pool-1-thread-4:8
pool-1-thread-5:3
pool-1-thread-5:4
pool-1-thread-5:5
pool-1-thread-5:6
pool-1-thread-5:7
pool-1-thread-5:8
pool-1-thread-5:9
pool-1-thread-5:10
pool-1-thread-5:11
pool-1-thread-5:12
pool-1-thread-5:13
pool-1-thread-5:14
pool-1-thread-5:15
pool-1-thread-5:16
pool-1-thread-1:3
pool-1-thread-5:17
pool-1-thread-5:18
pool-1-thread-5:19
pool-1-thread-5:20
pool-1-thread-5:21
pool-1-thread-5:22
pool-1-thread-5:23
pool-1-thread-5:24
pool-1-thread-5:25
pool-1-thread-4:9
pool-1-thread-4:10
pool-1-thread-4:11
pool-1-thread-4:12
pool-1-thread-4:13
pool-1-thread-4:14
pool-1-thread-4:15
pool-1-thread-4:16
pool-1-thread-4:17
pool-1-thread-4:18
pool-1-thread-6:9
pool-1-thread-6:10
pool-1-thread-6:11
pool-1-thread-3:14
pool-1-thread-3:15
pool-1-thread-2:3
pool-1-thread-3:16
pool-1-thread-3:17
pool-1-thread-3:18
pool-1-thread-3:19
pool-1-thread-3:20
pool-1-thread-3:21
pool-1-thread-3:22
pool-1-thread-3:23
pool-1-thread-3:24
pool-1-thread-3:25
pool-1-thread-3:26
pool-1-thread-3:27
pool-1-thread-3:28
pool-1-thread-3:29
pool-1-thread-3:30
pool-1-thread-3:31
pool-1-thread-3:32
pool-1-thread-3:33
pool-1-thread-6:12
pool-1-thread-4:19
pool-1-thread-4:20
pool-1-thread-4:21
pool-1-thread-4:22
pool-1-thread-4:23
pool-1-thread-4:24
pool-1-thread-4:25
pool-1-thread-4:26
pool-1-thread-4:27
pool-1-thread-5:26
pool-1-thread-5:27
pool-1-thread-5:28
pool-1-thread-1:4
pool-1-thread-1:5
pool-1-thread-1:6
pool-1-thread-5:29
pool-1-thread-4:28
pool-1-thread-6:13
pool-1-thread-6:14
pool-1-thread-3:34
pool-1-thread-2:4
pool-1-thread-3:35
pool-1-thread-6:15
pool-1-thread-6:16
pool-1-thread-6:17
pool-1-thread-6:18
pool-1-thread-6:19
pool-1-thread-6:20
pool-1-thread-4:29
pool-1-thread-4:30
pool-1-thread-4:31
pool-1-thread-4:32
pool-1-thread-5:30
pool-1-thread-5:31
pool-1-thread-5:32
pool-1-thread-5:33
pool-1-thread-5:34
pool-1-thread-5:35
pool-1-thread-5:36
pool-1-thread-5:37
pool-1-thread-5:38
pool-1-thread-5:39
pool-1-thread-5:40
pool-1-thread-5:41
pool-1-thread-5:42
pool-1-thread-5:43
pool-1-thread-5:44
pool-1-thread-5:45
pool-1-thread-5:46
pool-1-thread-5:47
pool-1-thread-5:48
pool-1-thread-5:49
pool-1-thread-1:7
pool-1-thread-1:8
pool-1-thread-1:9
pool-1-thread-1:10
pool-1-thread-1:11
pool-1-thread-1:12
pool-1-thread-1:13
pool-1-thread-1:14
pool-1-thread-1:15
pool-1-thread-1:16
pool-1-thread-1:17
pool-1-thread-1:18
pool-1-thread-1:19
pool-1-thread-1:20
pool-1-thread-1:21
pool-1-thread-1:22
pool-1-thread-1:23
pool-1-thread-1:24
pool-1-thread-1:25
pool-1-thread-1:26
pool-1-thread-1:27
pool-1-thread-1:28
pool-1-thread-1:29
pool-1-thread-1:30
pool-1-thread-1:31
pool-1-thread-1:32
pool-1-thread-1:33
pool-1-thread-1:34
pool-1-thread-1:35
pool-1-thread-1:36
pool-1-thread-1:37
pool-1-thread-1:38
pool-1-thread-1:39
pool-1-thread-1:40
pool-1-thread-1:41
pool-1-thread-1:42
pool-1-thread-1:43
pool-1-thread-1:44
pool-1-thread-1:45
pool-1-thread-1:46
pool-1-thread-1:47
pool-1-thread-1:48
pool-1-thread-4:33
pool-1-thread-4:34
pool-1-thread-6:21
pool-1-thread-6:22
pool-1-thread-6:23
pool-1-thread-3:36
pool-1-thread-3:37
pool-1-thread-3:38
pool-1-thread-3:39
pool-1-thread-3:40
pool-1-thread-3:41
pool-1-thread-3:42
pool-1-thread-3:43
pool-1-thread-2:5
pool-1-thread-2:6
pool-1-thread-2:7
pool-1-thread-2:8
pool-1-thread-2:9
pool-1-thread-2:10
pool-1-thread-2:11
pool-1-thread-2:12
pool-1-thread-2:13
pool-1-thread-2:14
pool-1-thread-2:15
pool-1-thread-2:16
pool-1-thread-2:17
pool-1-thread-2:18
pool-1-thread-6:24
pool-1-thread-6:25
pool-1-thread-6:26
pool-1-thread-6:27
pool-1-thread-6:28
pool-1-thread-6:29
pool-1-thread-4:35
pool-1-thread-4:36
pool-1-thread-4:37
pool-1-thread-4:38
pool-1-thread-4:39
pool-1-thread-4:40
pool-1-thread-4:41
pool-1-thread-4:42
pool-1-thread-4:43
pool-1-thread-4:44
pool-1-thread-4:45
pool-1-thread-4:46
pool-1-thread-4:47
pool-1-thread-4:48
pool-1-thread-4:49
pool-1-thread-1:49
pool-1-thread-6:30
pool-1-thread-6:31
pool-1-thread-6:32
pool-1-thread-6:33
pool-1-thread-2:19
pool-1-thread-2:20
pool-1-thread-2:21
pool-1-thread-2:22
pool-1-thread-2:23
pool-1-thread-2:24
pool-1-thread-2:25
pool-1-thread-2:26
pool-1-thread-2:27
pool-1-thread-2:28
pool-1-thread-3:44
pool-1-thread-2:29
pool-1-thread-2:30
pool-1-thread-2:31
pool-1-thread-2:32
pool-1-thread-2:33
pool-1-thread-2:34
pool-1-thread-2:35
pool-1-thread-2:36
pool-1-thread-2:37
pool-1-thread-2:38
pool-1-thread-2:39
pool-1-thread-2:40
pool-1-thread-6:34
pool-1-thread-6:35
pool-1-thread-6:36
pool-1-thread-6:37
pool-1-thread-6:38
pool-1-thread-6:39
pool-1-thread-6:40
pool-1-thread-6:41
pool-1-thread-6:42
pool-1-thread-6:43
pool-1-thread-6:44
pool-1-thread-6:45
pool-1-thread-6:46
pool-1-thread-6:47
pool-1-thread-6:48
pool-1-thread-6:49
pool-1-thread-2:41
pool-1-thread-3:45
pool-1-thread-3:46
pool-1-thread-3:47
pool-1-thread-3:48
pool-1-thread-3:49
pool-1-thread-2:42
pool-1-thread-2:43
pool-1-thread-2:44
pool-1-thread-2:45
pool-1-thread-2:46
pool-1-thread-2:47
pool-1-thread-2:48
pool-1-thread-2:49
Process finished with exit code 0
二.Callable接口
区别:Callable接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的.但是Runnable不会返回结果,并且无法抛出经过检查的异常,但Callable接口返回结果并且可能抛出异常的任务,并且实现者定义了一个不带任何参数的叫做call的方法.call计算结果,如果无法计算结果,则抛出一个异常
public interface Callable{
public V call() throws Exception;
}
-
jdk5加入,与Runnable接口类似,实现之后代表一个线程任务
-
Callable 具有泛型返回值,可以声明异常
package ThreadPool; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class TestCallable { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(3); MyTask1 myTask1 = new MyTask1(); executorService.submit(myTask1); } } class MyTask1 implements Callable<Integer>{ @Override public Integer call() throws Exception { for (int i = 0; i < 50; i++) { if (i == 30) { Thread.sleep(3000); } System.out.println(Thread.currentThread().getName()+":"+i); } return null; } } /** pool-1-thread-1:0 pool-1-thread-1:1 pool-1-thread-1:2 pool-1-thread-1:3 pool-1-thread-1:4 pool-1-thread-1:5 pool-1-thread-1:6 pool-1-thread-1:7 pool-1-thread-1:8 pool-1-thread-1:9 pool-1-thread-1:10 pool-1-thread-1:11 pool-1-thread-1:12 pool-1-thread-1:13 pool-1-thread-1:14 pool-1-thread-1:15 pool-1-thread-1:16 pool-1-thread-1:17 pool-1-thread-1:18 pool-1-thread-1:19 pool-1-thread-1:20 pool-1-thread-1:21 pool-1-thread-1:22 pool-1-thread-1:23 pool-1-thread-1:24 pool-1-thread-1:25 pool-1-thread-1:26 pool-1-thread-1:27 pool-1-thread-1:28 pool-1-thread-1:29 pool-1-thread-1:30 pool-1-thread-1:31 pool-1-thread-1:32 pool-1-thread-1:33 pool-1-thread-1:34 pool-1-thread-1:35 pool-1-thread-1:36 pool-1-thread-1:37 pool-1-thread-1:38 pool-1-thread-1:39 pool-1-thread-1:40 pool-1-thread-1:41 pool-1-thread-1:42 pool-1-thread-1:43 pool-1-thread-1:44 pool-1-thread-1:45 pool-1-thread-1:46 pool-1-thread-1:47 pool-1-thread-1:48 pool-1-thread-1:49
三.Future接口
- 概念:异步接受ExecutorService.submit()所返回的状态结果,当中包含了call()的返回值
方法:V get()以阻塞形式等待Future中的异步处理结果(call()的返回值)
需求:使用俩个线程,并发计算1-50,51-100的和,再进行汇总统计
package ThreadPool;
import java.util.concurrent.*;
public class TestFuture {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(3);
MTask mTask = new MTask();
MTask2 mTask2 = new MTask2();
//通过submit执行提交任务,Future接受返回的结果
Future<Integer> future = executorService.submit(mTask);
Future<Integer> future2 = executorService.submit(mTask2);
//通过Future的get方法获得线程执行完毕的结果
Integer value = future.get();
Integer value2 = future2.get();
System.out.println(value);
System.out.println(value2);
System.out.println(value+value2);
}
}
class MTask implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 1; i <= 50; i++) {
sum =sum+i;
}
return sum;
}
}
class MTask2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("MyCall正在执行");
int sum = 0;
for (int i = 51; i <= 100; i++) {
sum = sum + i;
}
return sum;
}
}
/**
MyCall正在执行
1275
3775
5050
十二.线程的同步和异步
同步(单条执行路径):
形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续.
异步(多条执行路径):
形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知之后立刻,返回,二者竞争时间片,并发执行.
十三.Lock接口
- jdk5加入,与synchronized比较,显示定义,结构更灵活
- 提供更多实用性方法,功能更强大,性能更优越
- 常用方法:
- void lock()//获取锁,如锁被占用,则等待
- boolean tryLock()//尝试获取锁(成功返回true,失败返回false,不阻塞)
- void unlock()//释放锁
使用说明:1.使用Lock,需要明确的写上锁和释放锁
2.为了避免拿到锁的线程在运行期间出现异常,导致程序的终止,没有释放锁,应用try{}finally
{}来保证,无论正确执行与否,最终都会释放锁
3.最好不出现递归,拿到锁之后最多释放锁
重入锁:
- ReentranLock:Lock接口的实现类,与synchronized一样具有互斥锁功能
package TestLock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestLocks {
public static void main(String[] args) {
Test test = new Test();
Thread thread = new Thread(new MyTask(test));
Thread thread2 = new Thread(new MyTask2(test));
thread.start();
thread2.start();
}
}
class Test{
Lock lock =new ReentrantLock();
public void method() {
//显示的写上,在此处获得锁
System.out.println(Thread.currentThread().getName()+"进入到上锁的方法是");
lock.lock();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"进入到退锁的方法是");
lock.unlock();
//显示的写上,在此处释放锁
}
}
class MyTask implements Runnable{
Test object;
public MyTask(Test object) {
this.object = object;
}
@Override
public void run() {
object.method();
}
}
class MyTask2 implements Runnable{
Test object;
public MyTask2(Test object) {
this.object = object;
}
@Override
public void run() {
object.method();
}
}
/**
Thread-1进入到上锁的方法是
Thread-0进入到上锁的方法是
Thread-1进入到退锁的方法是
Thread-0进入到退锁的方法是
Thread-1进入到上锁的方法是
Thread-0进入到上锁的方法是
Thread-1进入到退锁的方法是
Thread-0进入到退锁的方法是
读写锁
ReentrantReadWriteLock:
- 一种支持一写多读的同步锁,读写分离,可分别分配读锁,写锁
- 支持多次分配读锁,使多个读操作可以并发执行
互斥规则
-
写-写:互斥,阻塞
-
读-写,互斥,读阻塞写,写阻塞读.
-
读-读:不互斥,不阻塞
-
在读操作远远高于写操作的环境中, 在保障线程安全的情况下,提高运行效率
package TestLock;
import jdk.nashorn.internal.ir.CallNode;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class TestReentrantReadWriteLock {
public static void main(String[] args) {
Student stu = new Student();
ExecutorService ex = Executors.newFixedThreadPool(20);
writeTask1 Task1 = new writeTask1(stu);//写线程任务
ReadTask readTask = new ReadTask(stu);//读线程任务
//执行的俩个赋值的线程任务
long start = System.currentTimeMillis();//开始时间:毫秒祯
ex.submit(Task1);
ex.submit(Task1);
for (int i = 0; i < 18; i++) {
ex.submit(readTask);
}
//停止线程池,但是不停止已提交的任务,等已提交的任务都执行完
ex.shutdown();
//询问线程池 ,任务结束了吗?
while(true){
System.out.println("结束了吗?");//ex.isTerminated());//返回的是布尔值
if (ex.isTerminated() == true) {
break;
}
}
long end=System.currentTimeMillis();//结束时间
System.out.println(end-start);
}
}
//读操作任务
class ReadTask implements Callable{
Student stu;
public ReadTask(Student stu) {
this.stu = stu;
}
@Override
public Object call() throws Exception {
stu.getAge();
return null;
}
}
class writeTask1 implements Callable{
Student stu;
public writeTask1(Student stu) {
stu.setAge(100);
this.stu = stu;
}
@Override
public Object call() throws Exception {
return null;
}
}
class Student {
private int age;
// Lock lock = new ReentrantLock();//写和读的操作都加锁,性能过低.重入锁
ReentrantReadWriteLock rrw1 =new ReentrantReadWriteLock();//读写锁
ReentrantReadWriteLock.ReadLock readLock = rrw1.readLock();//读锁
ReentrantReadWriteLock.WriteLock writeLock =rrw1.writeLock();
//取值 --读操作
public int getAge() {
readLock.lock();
try {
try {
Thread.sleep(300);
this.age = age;
} catch (InterruptedException e) {
e.printStackTrace();
}
return this.age;
} finally {
readLock.unlock();
}
}
//赋值--写操作
public void setAge(int age) {
writeLock.lock();
try {
Thread.sleep(300);
this.age = age;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock .unlock();
}
}
}
十三:线程安全的集合
一.Collection体系集合下,除Vector以外的线程安全集合.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2130sgjI-1676340621903)(D:\JAVA\JavaStudy\笔记\img\Collections线程安全的集合.jpg)]
二.Collections中的工具方法
ex.shutdown();
//询问线程池 ,任务结束了吗?
while(true){
System.out.println("结束了吗?");//ex.isTerminated());//返回的是布尔值
if (ex.isTerminated() == true) {
break;
}
}
long end=System.currentTimeMillis();//结束时间
System.out.println(end-start);
}
}
//读操作任务
class ReadTask implements Callable{
Student stu;
public ReadTask(Student stu) {
this.stu = stu;
}
@Override
public Object call() throws Exception {
stu.getAge();
return null;
}
}
class writeTask1 implements Callable{
Student stu;
public writeTask1(Student stu) {
stu.setAge(100);
this.stu = stu;
}
@Override
public Object call() throws Exception {
return null;
}
}
class Student {
private int age;
// Lock lock = new ReentrantLock();//写和读的操作都加锁,性能过低.重入锁
ReentrantReadWriteLock rrw1 =new ReentrantReadWriteLock();//读写锁
ReentrantReadWriteLock.ReadLock readLock = rrw1.readLock();//读锁
ReentrantReadWriteLock.WriteLock writeLock =rrw1.writeLock();
//取值 --读操作
public int getAge() {
readLock.lock();
try {
try {
Thread.sleep(300);
this.age = age;
} catch (InterruptedException e) {
e.printStackTrace();
}
return this.age;
} finally {
readLock.unlock();
}
}
//赋值–写操作
public void setAge(int age) {
writeLock.lock();
try {
Thread.sleep(300);
this.age = age;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
writeLock .unlock();
}
}
}
# 十三:线程安全的集合
## 一.Collection体系集合下,除Vector以外的线程安全集合.
[外链图片转存中...(img-2130sgjI-1676340621903)]
## 二.Collections中的工具方法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WmNXMmPx-1676340621904)(D:\JAVA\JavaStudy\笔记\img\Collections线程安全的集合工具类.jpg)]