为什么要处理线程间通信:
当我们需要多个线程
来共同完成一件任务,并且我们希望他们有规律的执行
,那么多线程之间需要一些通信机制,可以协调它们的工作,以此实现多线程共同操作一份数据。
比如:线程A用来生产包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,此时B线程必须等到A线程完成后才能执行,那么线程A与线程B之间就需要线程通信,即—— 等待唤醒机制。
这是多个线程间的一种协作机制
。谈到线程我们经常想到的是线程间的竞争(race)
,比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制。
等待唤醒机制(Wait-Notify Mechanism)是一种用于多线程环境中的同步策略,主要用于实现线程间的通信和同步。这种机制允许一个线程(或多个线程)在特定条件未满足时暂停其执行(等待状态),而当条件满足时,另一个线程可以通知处于等待状态的线程恢复执行(唤醒操作)。等待唤醒机制是多线程编程中的一个核心概念,有助于保证线程安全和资源的有效利用。
主要组成部分
等待唤醒机制主要包括以下几个部分:
-
Wait(): 当一个线程调用对象的
wait()
方法时,它会释放当前持有的锁,并进入等待状态,直到其他线程通过notify()
或notifyAll()
方法来唤醒它。 -
wait(long timeout)
这种方法会在指定的时间后自动唤醒线程,即使没有调用
notify()
方法。 -
唤醒 (Notify): 当条件满足时,线程可以通过调用对象的
notify()
或notifyAll()
方法来通知一个或所有处于等待状态的线程,使它们可以重新
获取锁并继续执行。
区分sleep()和wait()
相同点:一旦执行,都会使得当前线程结束执行状态,进入阻塞状态。
不同点:
① 定义方法所属的类:sleep()
:Thread中定义。 wait()
:Object中定义
② 使用范围的不同:sleep()
可以在任何需要使用的位置被调用; wait()
:必须使用在同步代码块或同步方法中
③ 都在同步结构中使用的时候,是否释放同步监视器的操作不同:sleep()
:不会释放同步监视器 ;wait()
:会释放同步监视器
④ 结束等待的方式不同:sleep()
:指定时间一到就结束阻塞。 wait()
:可以指定时间也可以无限等待直到notify或notifyAll。
1. 使用 wait()
和 notify()
这是最基本的等待唤醒机制,它们定义在 Object
类中,因此任何对象都可以作为锁对象使用。
public synchronized void doSomething() {
while (!someCondition) {
try {
this.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
// 处理中断
}
}
// 执行操作
}
public synchronized void changeCondition() {
someCondition = true;
this.notify(); // 唤醒一个等待的线程
}
2. 使用 wait(long timeout)
这种方法会在指定的时间后自动唤醒线程,即使没有调用 notify()
方法。
public synchronized void doSomething() {
long start = System.currentTimeMillis();
while (!someCondition) {
try {
long timeout = someConditionTimeout - (System.currentTimeMillis() - start);
if (timeout > 0) {
this.wait(timeout);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
// 处理中断
}
}
// 执行操作
}
3. 使用 Condition
接口
Condition
是 java.util.concurrent.locks
包中提供的高级同步工具,可以更灵活地控制线程间的同步。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private boolean someCondition = false;
public void doSomething() {
lock.lock();
try {
while (!someCondition) {
condition.await();
}
// 执行操作
} finally {
lock.unlock();
}
}
public void changeCondition() {
lock.lock();
try {
someCondition = true;
condition.signal(); // 唤醒一个等待的线程
} finally {
lock.unlock();
}
}
4. 使用 LockSupport
LockSupport
类提供了一种不同的线程阻塞和唤醒机制,可以更精细地控制线程。
import java.util.concurrent.locks.LockSupport;
public void doSomething() {
while (!someCondition) {
LockSupport.park(this); // 阻塞当前线程
}
// 执行操作
}
public void changeCondition() {
someCondition = true;
LockSupport.unpark(Thread.currentThread()); // 唤醒线程
}
注意事项
- 锁的作用域:
wait()
和notify()
必须在同步代码块或同步方法中调用。 - 中断响应:线程在等待时可能会被中断,需要适当地处理
InterruptedException
。 - 锁对象一致性:
wait()
和notify()
必须由同一个锁对象调用。 - 避免死锁:确保线程按照一致的顺序获取锁,防止死锁发生。
- 正确使用
notifyAll()
:如果使用notifyAll()
,所有等待线程都会有机会获得锁,但不一定所有线程都能继续执行,因为锁只有一把。
使用场景
等待唤醒机制通常用于以下场景:
-
生产者-消费者模型
:生产者生产数据,消费者消费数据。消费者在没有数据可用时等待,而生产者在数据准备好时唤醒消费者
-
同步数据访问:在线程之间共享资源时,确保线程只在适当条件下访问数据。
-
线程间的条件同步:线程A等待某个条件满足,线程B在满足条件后唤醒线程A。