要实现多个线程之间的协同,需要涉及到线程之间相互通信,线程间通信分为以下四类:
- 文件共享
- 网络共享
- 共享全局变量
- jdk提供的线程协调API
本文只讲解jdk提供的API。
三种线程协作通信的方式:
- suspend/resume(已弃用)
- wait/notify
- park/unpark
suspend/resume
示例(生产者—消费者模型):线程1买包子,发现没有包子,停止执行,线程2生产出包子,通知线程1继续执行。
public class Test {
/** 包子 */
public static Object baozi = null;
public static void main(String[] args) throws Exception {
suspendResumeTest();
}
/** 正常的suspend/resume */
public static void suspendResumeTest() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
while (baozi == null) { // 如果没包子,则进入等待
System.out.println("没有包子,进入等待");
Thread.currentThread().suspend();
}
System.out.println("买到包子");
});
consumerThread.start();
// 1秒之后,生产一个包子
Thread.sleep(1000L);
baozi = new Object();
consumerThread.resume();
System.out.println("生产出包子,通知消费者买包子");
}
}
正常消费情况下没有出现问题,但是使用suspend/resume 容易出现死锁情况。
死锁情况1:
当消费者线程(consumerThread)中存在同步代码块时,suspend并不会释放锁。而其他线程由于抢不到锁会一直处于阻塞状态。也就一直无法通知消费者线程继续执行。
public class Test {
/** 包子 */
public static Object baozi = null;
public static void main(String[] args) throws Exception {
new Test().suspendResumeDeadLockTest();
}
public void suspendResumeDeadLockTest() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
while (baozi == null) { // 如果没包子,则进入等待
System.out.println("没包子,进入等待");
// 当前线程拿到锁,然后挂起
synchronized (this) {
Thread.currentThread().suspend();
}
}
System.out.println("买到包子");
});
consumerThread.start();
// 1秒之后,生产一个包子
Thread.sleep(1000L);
baozi = new Object();
// 争取到锁以后,再恢复consumerThread
synchronized (this) {
consumerThread.resume();
}
System.out.println("生产出包子,通知消费者买包子");
}
}
死锁情况2:
resume先执行,suspend后执行也会导致程序永久挂起。
public class Test {
/** 包子 */
public static Object baozi = null;
public static void main(String[] args) throws Exception {
new Test().suspendResumeDeadLockTest2();
}
public void suspendResumeDeadLockTest2() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
while (baozi == null) {
System.out.println("没包子,进入等待");
try { // 三秒后挂起
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 这里的挂起执行在resume后面
Thread.currentThread().suspend();
}
System.out.println("买到包子");
});
consumerThread.start();
// 1秒之后,生产一个包子
Thread.sleep(1000L);
baozi = new Object();
consumerThread.resume();
System.out.println("生产出包子,通知消费者买包子");
consumerThread.join();
}
}
wait/notify机制
wait方法会导致当前线程等待,加入该对象的等待集合中,并且放弃当前持有的对象锁,notify/notifyAll方法唤醒一个或所有正在等待这个对象锁的线程。
注意:
虽然wait会自动解锁,但是对顺序有要求,如果notify在wait调用前执行,那么线程也会永远处于等待状态。
public class Test {
/** 包子 */
public static Object baozi = null;
public static void main(String[] args) throws Exception {
new Test().waitNotifyTest();
}
/** 正常的wait/notify */
public void waitNotifyTest() throws Exception {
// 启动线程
new Thread(() -> {
while (baozi == null) { // 如果没包子,则进入等待
synchronized (this) {
try {
System.out.println("没有包子,进入等待\"");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("买到包子");
}).start();
// 1秒之后,生产一个包子
Thread.sleep(1000L);
baozi = new Object();
synchronized (this) {
this.notifyAll();
System.out.println("生产出包子,通知消费者买包子");
}
}
}
park/unpark机制
线程调用park等待“许可”,unpark方法为指定线程提供“许可”。
不要求park/unpark方法的调用顺序,多次调用unpark不会叠加,之后第一次调用park,线程会直接运行,后续调用则会进入等待。
注意:
unpark不会主动释放锁
import java.util.concurrent.locks.LockSupport;
public class Test {
/** 包子 */
public static Object baozi = null;
public static void main(String[] args) throws Exception {
new Test().parkUnparkTest();
}
/** 正常的park/unpark */
public void parkUnparkTest() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
while (baozi == null) {
try {
//等待三秒
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("进入等待");
LockSupport.park();
}
System.out.println("买到包子");
});
consumerThread.start();
// 1秒之后,生产一个包子
Thread.sleep(1000L);
baozi = new Object();
LockSupport.unpark(consumerThread);
System.out.println("生产出包子,通知消费者买包子");
}
}