多线程环境下,线程通讯的方式主要采用两种方式,一种是内存共享型,一种是消息传递型。
内存共享
使用这种方式进行线程通讯,通常会设置一个共享变量,多个线程操作同一个共享变量,来达到多线程之间通讯的目的。这种方式需要考虑多线程之前的同步问题,这个共享变量必须通过加锁或同步的方式进行保护,一般采用sychronized、volatile关键字进行修饰。但共享变量多的情况下,易发生死锁问题。
消息传递
一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作,整个 过程开始于一个线程,而最终执行又是另一个线程。前者是生产者,后者就是消费者。
Java通过内置的等待/通知机制很好的实现了这个功能。等待/通知的相关方法是任意Java对象都具备的,因为这些方法被定义在所有对象的超类java.lang.Object上。
等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。
等待方遵循如下原则:
- 获取对象的锁;
- 如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件;
- 条件满足则执行对应的逻辑;
对应伪代码如下:
synchronized(对象) {
while(条件不满足) {
对象.wait();
}
对应的处理逻辑
}
通知方遵循如下原则:
- 获得对象的锁;
- 改变条件;
- 通知所有等待在对象上的线程;
对应的伪代码如下:
synchronized(对象){
改变条件
对象.notifyAll();
}
举例如下:
线程t1,t2分别循环打印0和1,一个递增,一个递减,
class Scratch {
public static void main(String[] args) {
WaitNotifyTest waitNotifyTest =new WaitNotifyTest();
Thread t1 = new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
waitNotifyTest.increase();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1");
Thread t2 = new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
waitNotifyTest.decrease();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t2");
t1.start();
t2.start();
}
}
class WaitNotifyTest{
private int value = 0;
public synchronized void increase() throws InterruptedException {
while (value != 0) {
wait();
}
value++;
System.out.println("线程-"+Thread.currentThread().getName()+" value-"+value);
notifyAll();
}
public synchronized void decrease() throws InterruptedException {
while (value != 1) {
wait();
}
value--;
System.out.println("线程-"+Thread.currentThread().getName()+" value-"+value);
notifyAll();
}
}
输出如下:
线程-t1 value-1
线程-t2 value-0
线程-t1 value-1
线程-t2 value-0
线程-t1 value-1
线程-t2 value-0
线程-t1 value-1
......
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了