1. 线程状态图
wait: Object方法,当前线程执行wait将释放锁,进入等待池中,等待其他线程唤醒
sleep: Thread方法,当前线程只是休眠并不释放锁
yield: Thread方法,优先执行其他线程,不释放锁
join: Thread方法,是一种特殊wait。比如有两个线程t1、t2,当在t1中调用了t2.join,t1线程进入阻塞状态,直到t2执行完后,t1才继续运行
2. 多线程间通讯
线程一旦开始就会拥有自己独立的本地内存(主内存copy变量副本,线程操作都是在本地内存中进行),那么多个线程如何相互配合完成工作,这就涉及到线程间通讯
线程间通讯:多个线程在操作同一份数据时,互相告知自己的状态,避免对同一共享变量的争夺
线程通讯主要有三种方式:共享内存(主内存)、消息传递、管道流
2.1 volatile && synchronized关键字 && Lock --共享内存
synchronized实现线程间通讯是指多个线程通过synchronized关键字实现对共享变量加锁,哪个线程获取到锁哪个线程就执行;Lock也是同理
要注意volatile关键字不能保证原子性,所以对于非原子性操作不能实现线程间安全通讯
public class TestSync { //定义共享变量来实现通信,它需要volatile修饰,否则线程不能及时感知 static volatile boolean notice = false; public static void main(String[] args) { List<String> list = new ArrayList<>(); //线程A Thread threadA = new Thread(() -> { for (int i = 1; i <= 10; i++) { list.add("abc"); System.out.println("线程A添加元素,此时list的size为:" + list.size()); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } if (list.size() == 5) notice = true; } }); //线程B Thread threadB = new Thread(() -> { while (true) { if (notice) { System.out.println("线程B收到通知,开始执行自己的业务..."); break; } } }); //需要先启动线程B threadB.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // 再启动线程A threadA.start(); }
2.2 wati notify -- 消息传递
2.2.1 基础理论
wait notify实现线程间通讯的理论是:一个线程修改了对象的值,另一个线程感知到了变化,然后进行相应操作,整个过程始于一个线程,终于另一个线程。wait-notify机制使用的是同一个对象锁。
wait:将当前正在执行线程从调用出中断并且释放锁资源,直到收到notify/nofifyall后才从等待队列转入锁池队列。这时只是去竞争锁,但不一定成功
wait(long time):在经过time(ms)后没有收到notify/nofyall的通知,自动从等待队列转入锁池队列
notify:随机从等待队列(比如Thread A、Thread B都调用了wait方法,那么此时A B都进入等待队列,此时只有在另外线程C调用notify/notifyAll才会让A B解除wait状态重新参与竞争对象锁)中通知一个持有相同锁的一个线程
notifyAll:通知等待队列中持有相同锁的所有线程,让这些线程转入锁池队列
注意wait和wait(time)都要注意中断问题,wait中断是从语句处中断并且释放锁,当再次获取到锁时时从中断处继续向下执行
notify和notifyAll方法通知是延迟通知,必须等待当前线程体执行完所有同步代码推出释放锁才通知wait线程
demo1: notify vs notifyAll

public class WaitDemo1 { private static class WaitTask implements Runnable { private Object lock; public WaitTask(Object lock) { this.lock = lock; } @Override public void run() { synchronized (lock) { System.out.println(Thread.currentThread().getName() + "准备进入等待状态"); try { // 调用wait之后线程进入waiting状态,等待被其他线程唤醒(lock.notify()) lock.wait(); } catch (InterruptedException e) { throw new RuntimeException(e); } // 被唤醒之后,线程从wait方法之后开始继续执行。 System.out.println(Thread.currentThread().getName() + "等待结束,本线程继续执行"); } } } private static class NotifyTask implements Runnable { private Object lock; public NotifyTask(Object lock) { this.lock = lock; } @Override public void run() { synchronized (lock) { System.out.println("准备唤醒"); // synchronized修饰的代码块执行结束之后才会释放锁。 lock.notifyAll(); // 是唤醒全部 // lock.notify(); // 是随机唤醒一个 System.out.println("唤醒结束"); } } } public static void main(String[] args) throws InterruptedException { Object lock = new Object(); Thread thread1 = new Thread(new WaitTask(lock),"thread1"); Thread thread2 = new Thread(new WaitTask(lock),"thread2"); Thread thread3 = new Thread(new WaitTask(lock),"thread3"); Thread notify = new Thread(new NotifyTask(lock),"notify线程"); thread1.start(); thread2.start(); thread3.start(); Thread.sleep(100); notify.start(); } }
notify只是随机唤醒某一个处于wait状态的等待线程;notifyAll是一次唤醒所有的等待线程
output:
thread1准备进入等待状态
thread3准备进入等待状态
thread2准备进入等待状态
准备唤醒
唤醒结束
thread2等待结束,本线程继续执行
thread3等待结束,本线程继续执行
thread1等待结束,本线程继续执行
如果将notifyAll改为notify则只会随机唤醒一个wait状态线程,此时output如下:
thread1准备进入等待状态
thread3准备进入等待状态
thread2准备进入等待状态
准备唤醒
唤醒结束
thread1等待结束,本线程继续执行
步骤:
1.t1拿到lock,其中 t3,t2 就进入了阻塞队列。
2.当调用 wait 方法,调用 wait 方法会释放锁,则 t3,t2 就会竞争锁,而 t1 进入等待队列。
3.此时 t3 拿到锁,t2还在阻塞队列。
4.调用 wait 方法,t3 也进入等待队列,释放锁,t2 拿到锁。
5.t2 在调用 wait 方法,这样 三个线程都进入了等待队列。
6.调用 notify方法,会同时唤醒3个线程从等待队列到阻塞队列,此时3个线程需要重新竞争锁。(synchronized 不是公平锁 )
7.t2 ,t3, t1 依次抢到锁资源,执行操作。
demo2: notify延迟通知
package com.threadconmmunication.watinotify; import java.util.ArrayList; import java.util.List; public class TestSync { static volatile boolean notice = false; public static void main(String[] args) { //定义一个锁对象 Object lock = new Object(); List<String> list = new ArrayList<>(); // 线程A Thread threadA = new Thread(() -> { synchronized (lock) { for (int i = 1; i <= 10; i++) { list.add("abc"); System.out.println("线程A添加元素,此时list的size为:" + list.size()); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } if (list.size() == 5) lock.notify();//唤醒B线程 } } }); //线程B Thread threadB = new Thread(() -> { while (true) { synchronized (lock) { if (list.size() != 5) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //threadB感知到threadA对list的变化,这里可以进行相应list的业务代码操作
System.out.println("线程B收到通知,开始执行自己的业务..."); } } }); //需要先启动线程B threadB.start(); try { Thread.sleep(1000); //sleep 是为了保证线程B先启动 } catch (InterruptedException e) { e.printStackTrace(); } //再启动线程A threadA.start(); } }
输出:
线程A添加元素,此时list的size为:1
线程A添加元素,此时list的size为:2
线程A添加元素,此时list的size为:3
线程A添加元素,此时list的size为:4
线程A添加元素,此时list的size为:5
线程A添加元素,此时list的size为:6
线程A添加元素,此时list的size为:7
线程A添加元素,此时list的size为:8
线程A添加元素,此时list的size为:9
线程A添加元素,此时list的size为:10
线程B收到通知,开始执行自己的业务...
如上红色标记代码在list.size()=5时调用notify(),但从结果看知道list()=10之后才重新执行线程B wait之后的代码,这说明notify/notifyAll是延迟通知,只有当前线程所有代码执行完成后才释放锁
2.2.2 非法监视异常
wait notify必须和在synchronized代码块中,否则出现非法监视异常(illegalMonitorStateException)
原因:以如下采用wait() notify()实现阻塞队列为例,如果wait() notify不强制要求加锁,当thread1运行到wait()之前,由于时间片轮转,此时cpu调度到thread2运行,然后执行notify().当再交由thread1运行时,thread1 运行了wait,然后将永远无法醒过来。所以在使用wait notify时,他们必须在synchronized中竞争同一个对象锁(或者类锁)
class MyBlockingQueue { // 用来保存任务的集合 Queue<String> queue = new LinkedList<>(); /** * 添加方法 */ public void put(String data) { synchronized (MyBlockingQueue.class) { // 队列加入数据 queue.add(data); // 为了防止 take 方法阻塞休眠,这里需要调用唤醒方法 notify notify(); // ③ } } /** * 获取方法(阻塞式执行) * 如果队列里面有数据则返回数据,如果没有数据就阻塞等待数据 * @return */ public String take() throws InterruptedException { synchronized (MyBlockingQueue.class) { // 使用 while 判断是否有数据(这里使用 while 而非 if 是为了防止虚假唤醒) while (queue.isEmpty()) { // ① // 没有任务,先阻塞等待 wait(); // ② } } return queue.remove(); // 返回数据 } }
2.3 java.util.concurrent包下CountDownLatch
和wait-notify机制比较类似
创建一个计数器,设置初始值 CountdownLatch countDownLatch = new CountDownLatch(2);
线程调用 countDownLatch.await() 方法时,会将该线程置于同步队列阻塞等待(本质是获取共享锁)
当其他线程调用 countDownLatch.countDown() 方法时,会将计数值不断递减,直到为 0 时,此时才会唤醒因调用 countDownLatch.await() 的而在同步队列中的线程,去执行自己的任务
关于countdownLatch具体参考 https://www.cnblogs.com/enhance/p/16529543.html
3. 线程安全
4. 多线程共享变量控制
线程范围内的共享变量是指对同一个变量,几个线程同时对它进行写和读操作,而同一个线程读到的数据就是它自己写进去的数据
4.1 synchronized
4.2 volatile
4.3 ReentrantLock
具体参考: https://www.cnblogs.com/enhance/p/11265132.html
4.4 Atomic原子类
参考文献
https://blog.csdn.net/u012811805/article/details/126047153
https://blog.csdn.net/weixin_43808717/article/details/115351888
https://blog.csdn.net/sufu1065/article/details/123102819
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)