线程之间通信的主要方式有两种:
- 共享内存
- 消息传递
举例:
有两个线程 A 和 B,A 线程向 list 集合里添加元素 abc,一共添加10次,要求当添加到第5次的时候,通知线程 B 执行相关业务。
方式1:使用 volatitle 关键字
使用共享内存的思想,大致思路是多个线程同时监听一个变量,
当这个变量发生变化时,线程能够感知并执行相应的业务。
// 定义一个共享变量来实现通信
static volatile boolean notice = false;
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// 实现线程A
Thread threadA = new Thread(() ->{
for (int i = 0; i < 10; i++) {
list.add("abc");
System.out.println("A 向列表中添加一个元素, 此时list中元素个数为: " + list.size());
if (list.size() == 5) {
notice = true;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
// 实现线程B
Thread threadB = new Thread(() -> {
while (true) {
if (notice){
System.out.println("线程 B 收到通知, 开始执行自己的业务");
break;
}
}
});
// 需要先启动线程B
threadB.start();
// 再启动线程A
threadA.start();
// 等待子线程执行完成
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
经测试,volatile 通知会有一些延迟,但不会很长,在 1 ms 以内
方法2:使用 Object 类的 wait 和 notify 方法
基于消息传递的思想
需要注意:wait 和 notify 必须配合 synchronized 使用
public static void main(String[] args) {
// 定义一个锁对象
Object lock = new Object();
ArrayList<String> list = new ArrayList<>();
// 实现线程 A
Thread threadA = new Thread(() -> {
synchronized (lock){
for (int i = 0; i < 10; i++) {
list.add("abc");
System.out.println("A 向列表中添加一个元素, 此时list中元素个数为:" + list.size());
if (list.size() == 5) {
// 唤醒 B线程
lock.notify();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
// 实现线程B
Thread threadB = new Thread(() -> {
synchronized (lock) {
if(list.size() != 5){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程 B 收到通知,开始执行自己的业务...");
}
});
// 需要先启动线程B
threadB.start();
// 再启动线程A
threadA.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
经测试,需要等待线程 A 运行完成后才会执行线程 B
方法3:使用 JUC 工具类 CountDownLatch
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(1);
ArrayList<String> list = new ArrayList<>();
// 实现线程A
Thread threadA = new Thread(() -> {
for (int i = 0; i < 10; i++){
list.add("abc");
System.out.println("A 向列表中添加一个元素, 此时list中元素个数为:" + list.size());
if (list.size() == 5) {
countDownLatch.countDown();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
// 实现线程B
Thread threadB = new Thread(() -> {
if (list.size() != 5){
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程B收到通知,开始执行自己的业务...");
});
// 先启动线程B
threadB.start();
// 在启动线程A
threadA.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
经测试,CountDownLatch 通知会有一些延迟,但不会很长,在 1 ms 以内
方法4:使用 ReentrantLock 可重入锁,结合 Condition
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
ArrayList<String> list = new ArrayList<>();
// 实现线程A
Thread threadA = new Thread(() -> {
lock.lock();
for (int i = 0; i < 10; i++) {
list.add("abc");
System.out.println("A 向列表中添加一个元素, 此时list中元素个数为:" + list.size());
if (list.size() == 5) {
condition.signal();
lock.unlock();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
// 实现线程B
Thread threadB = new Thread(() -> {
lock.lock();
if (list.size() < 5){
try {
condition.await();
System.out.println("线程B收到通知,开始执行自己的业务...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lock.unlock();
});
// 先启动线程B
threadB.start();
// 在启动线程A
threadA.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
经测试,ReentrantLock 结合 Condition 的方式通知会有一些延迟,但不会很长,在 1 ms 以内
方法5:基本 LockSupport 实现线程间的阻塞和唤醒(推荐)
优点:灵活,不需要关注是等待线程先运行还是唤醒线程先运行
缺点:需要知道线程的名字
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
// 实现线程B
final Thread threadB = new Thread(() -> {
if (list.size() != 5) {
LockSupport.park();
}
System.out.println("线程B收到通知,开始执行自己的业务...");
});
// 实现线程A
Thread threadA = new Thread(() -> {
for (int i = 0; i < 10; i++){
list.add("abc");
System.out.println("A 向列表中添加一个元素, 此时list中元素个数为:" + list.size());
if (list.size() == 5) {
LockSupport.unpark(threadB);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
threadA.start();
threadB.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
经测试,LockSupport 的方式通知会有一些延迟,但不会很长,在 1 ms 以内
虽然先 unpark 不会报错,但是先执行 unpark 后,就不会执行 park 的语句块了