Java实现线程间通信方式
线程间通信的模型:
-
共享内存
-
消息传递
我们来做道题理解一下
题目: 有两个线程A、B,A线程向一个集合里面依次添加元素"abc"字符串,一共添加十次,当添加到第五次的时候,希望B线程能够收到A线程的通知,然后B线程执行相关的业务操作。
方法1: 使用volatile关键字
- 使用共享内存的思想,大致意思就是多个线程同时监听一个变量,当这个变量发生变化的时候 ,线程能够感知并执行相应的业务。
- 是最简单的一种实现方式。
package com.ronnie.leetcode.tel;
import java.util.ArrayList;
import java.util.List;
public class TestSync01 {
// 定义一个共享变量来实现通信,它需要是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("nga");
System.out.println("线程A向列表中添加一个元素, 此时list中元素个数为: " + 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("线程N收到通知, 开始执行自己的业务: ");
break;
}
}
});
// 需要先启动线程B
threadB.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 再启动线程A
threadA.start();
}
}
-
实际运行结果与我们想的有些出入
线程A向列表中添加一个元素, 此时list中元素个数为: 1 线程A向列表中添加一个元素, 此时list中元素个数为: 2 线程A向列表中添加一个元素, 此时list中元素个数为: 3 线程A向列表中添加一个元素, 此时list中元素个数为: 4 线程A向列表中添加一个元素, 此时list中元素个数为: 5 线程A向列表中添加一个元素, 此时list中元素个数为: 6 线程N收到通知, 开始执行自己的业务: 线程A向列表中添加一个元素, 此时list中元素个数为: 7 线程A向列表中添加一个元素, 此时list中元素个数为: 8 线程A向列表中添加一个元素, 此时list中元素个数为: 9 线程A向列表中添加一个元素, 此时list中元素个数为: 10
-
将try catch放到if判断之后则能达到预期的输出
线程A向list中添加一个元素,此时list中的元素个数为:1 线程A向list中添加一个元素,此时list中的元素个数为:2 线程A向list中添加一个元素,此时list中的元素个数为:3 线程A向list中添加一个元素,此时list中的元素个数为:4 线程A向list中添加一个元素,此时list中的元素个数为:5 线程B收到通知,开始执行自己的业务... 线程A向list中添加一个元素,此时list中的元素个数为:6 线程A向list中添加一个元素,此时list中的元素个数为:7 线程A向list中添加一个元素,此时list中的元素个数为:8 线程A向list中添加一个元素,此时list中的元素个数为:9 线程A向list中添加一个元素,此时list中的元素个数为:10
-
原因是:
- 在第5次的时候线程B从阻塞状态过渡了可以竞争锁的状态,但是它并不一定就能立刻获取CPU的执行权
-
方法2: 使用Object类的wait()和notify() 方法
- 实现方式: 消息传递
- 需要注意的是:
- wait和 notify必须配合synchronized使用,wait方法释放锁,notify方法不释放锁
package com.ronnie.leetcode.tel;
import java.util.ArrayList;
public class TestSync02 {
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 = 1; i <= 10; i++){
list.add("nga");
System.out.println("线程A向list中添加一个元素,此时list中的元素个数为:" + list.size());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (list.size() == 5)
// 唤醒 B线程
lock.notify();
}
}
});
// 实现线程B
Thread threadB = new Thread(() -> {
while (true){
synchronized (lock){
if(list.size() != 5){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程B收到通知,开始执行自己的业务...");
}
}
});
// 需要先启动线程B
threadB.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 再启动线程A
threadA.start();
}
}
-
执行结果
线程A向list中添加一个元素,此时list中的元素个数为:1 线程A向list中添加一个元素,此时list中的元素个数为:2 线程A向list中添加一个元素,此时list中的元素个数为:3 线程A向list中添加一个元素,此时list中的元素个数为:4 线程A向list中添加一个元素,此时list中的元素个数为:5 线程A向list中添加一个元素,此时list中的元素个数为:6 线程A向list中添加一个元素,此时list中的元素个数为:7 线程A向list中添加一个元素,此时list中的元素个数为:8 线程A向list中添加一个元素,此时list中的元素个数为:9 线程A向list中添加一个元素,此时list中的元素个数为:10 线程B收到通知,开始执行自己的业务...
-
线程A发出notify()唤醒通知之后,依然是走完了自己线程的业务之后,线程B才开始执行,这也正好说明了,notify()方法不释放锁,而wait()方法释放锁。
方法3: 使用JUC工具类 CountDownLatch()
package com.ronnie.leetcode.tel;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
public class TestSync03 {
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("pcr");
System.out.println("线程A向list中添加一个元素,此时list中的元素个数为:" + list.size());
if (list.size() == 5)
countDownLatch.countDown();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 实现线程B
Thread threadB = new Thread(() -> {
while (true){
if (list.size() != 5){
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程B收到通知,开始执行自己的业务...");
break;
}
});
// 先启动线程B
threadB.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 在启动线程A
threadA.start();
}
}
-
执行结果
线程A向list中添加一个元素,此时list中的元素个数为:1 线程A向list中添加一个元素,此时list中的元素个数为:2 线程A向list中添加一个元素,此时list中的元素个数为:3 线程A向list中添加一个元素,此时list中的元素个数为:4 线程A向list中添加一个元素,此时list中的元素个数为:5 线程B收到通知,开始执行自己的业务... 线程A向list中添加一个元素,此时list中的元素个数为:6 线程A向list中添加一个元素,此时list中的元素个数为:7 线程A向list中添加一个元素,此时list中的元素个数为:8 线程A向list中添加一个元素,此时list中的元素个数为:9 线程A向list中添加一个元素,此时list中的元素个数为:10 线程A向list中添加一个元素,此时list中的元素个数为:11
方法4: 使用ReentrantLock结合Condition
package com.ronnie.leetcode.tel;
import java.util.ArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class TestSync04 {
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("pcr");
System.out.println("线程A向list中添加一个元素,此时list中的元素个数为:" + list.size());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (list.size() == 5)
condition.signal();
}
});
// 实现线程B
Thread threadB = new Thread(() -> {
lock.lock();
if (list.size() != 5){
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程B收到通知,开始执行自己的业务...");
lock.unlock();
});
// 先启动线程B
threadB.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 在启动线程A
threadA.start();
}
}
-
执行结果
线程A向list中添加一个元素,此时list中的元素个数为:1 线程A向list中添加一个元素,此时list中的元素个数为:2 线程A向list中添加一个元素,此时list中的元素个数为:3 线程A向list中添加一个元素,此时list中的元素个数为:4 线程A向list中添加一个元素,此时list中的元素个数为:5 线程A向list中添加一个元素,此时list中的元素个数为:6 线程A向list中添加一个元素,此时list中的元素个数为:7 线程A向list中添加一个元素,此时list中的元素个数为:8 线程A向list中添加一个元素,此时list中的元素个数为:9 线程A向list中添加一个元素,此时list中的元素个数为:10 线程A向list中添加一个元素,此时list中的元素个数为:11
-
可以看到线程B在被A唤醒之后由于没有获取锁还是不能立即执行,也就是说,A在唤醒操作之后,并不释放锁。
-
与Object类的wait()和notify()一样。
方法5: 基本LockSupport实现线程间的阻塞和唤醒(推荐)
- 优点:
- 灵活
- 不需要关注是等待线程先进行还是唤醒线程先运行
- 需要知道线程的名字
package com.ronnie.leetcode.tel;
import java.util.ArrayList;
import java.util.concurrent.locks.LockSupport;
public class TestSync05 {
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 = 1; i <= 10; i++){
list.add("pcr");
System.out.println("线程A向list中添加一个元素,此时list中的元素个数为:" + list.size());
if (list.size() == 5)
LockSupport.unpark(threadB);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
threadA.start();
threadB.start();
}
}
-
执行结果
线程A向list中添加一个元素,此时list中的元素个数为:1 线程A向list中添加一个元素,此时list中的元素个数为:2 线程A向list中添加一个元素,此时list中的元素个数为:3 线程A向list中添加一个元素,此时list中的元素个数为:4 线程A向list中添加一个元素,此时list中的元素个数为:5 线程B收到通知,开始执行自己的业务... 线程A向list中添加一个元素,此时list中的元素个数为:6 线程A向list中添加一个元素,此时list中的元素个数为:7 线程A向list中添加一个元素,此时list中的元素个数为:8 线程A向list中添加一个元素,此时list中的元素个数为:9 线程A向list中添加一个元素,此时list中的元素个数为:10