接上篇文章,一般面试问了同步的话,通信肯定也是会问的,所以这篇文件主要讲线程之间的通信。
简介
线程间通信的模型有:共享内存和消息传递
具体有这几种方法:
-
利用关键字volatile进行共享内存,这种是最简单的,这种方式就是多个线程同时监听一个变量;
-
利用Object中的wait和notify进行线程间通讯,wait方式释放锁,而notify不释放锁。
-
CountDownLatch基于AQS框架,维护一个线程间的共享变量state
-
ReentranLock结合Condition来实现通信
-
LockSupport实现线程间的阻塞和唤醒
一、volatile关键字:基于共享内存
多个线程同时监听一个变量,当这个变量发生变化的时候,线程能够感知并执行相应的业务,这是最简单的一种实现方式:
/**
* @author lqkj
* @data 2022/10/21 9:29
* 基于共享内存
*/
public class Volatile_Syc {
//定义共享变量来实现通信,它需要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("123");
System.out.println("线程A添加元素,此时list的size为:" + 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("线程B收到通知,开始执行自己的业务逻辑。。");
break;
}
}
});
//需要先启动线程B
threadB.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//再启动线程A
threadA.start();
}
}
二、Object类的wait和notify
Object类中提供了线程间通信的方法:wait、notify、notifyAll,它们是多线程通信的基础,它们的实现思想是线程间通信。
注意:这几个方法都必须配合synchronized使用,wait方法释放锁,notify不释放锁,wait是指一个已经进入了同步锁的线程内,让自己暂时让出同步锁,以便其他正在等待此锁的线程可以得到该锁并运行,只有其他线程调用了notify方法,notify并不释放锁,只是告诉调用过wait方法的线程可以去参与获得锁的竞争了,但不会马上得到锁,因为锁还在其他线程那里,还没释放,调用wait方法的一个或多个线程才会解除wait状态,重新参与竞争对象锁,如果程序可以再次得到锁,那么就可以继续往下运行。代码如下:
/**
* @author lqkj
* @data 2022/10/21 9:44
* 通过Object的wait和notify进行线程通信
* 虽然线程A唤醒线程B以后,但是还是线程A执行完毕后才执行线程B的操作
* 这说明notify是不释放锁的
*/
public class WaitNotify_Syc {
public static void main(String[] args) {
//定义一个锁对象
Object lock = new Object();
List<String> list = new ArrayList<>();
//线程A
Thread threadA = new Thread( () ->{
for(int i = 1 ; i <= 10 ; i++){
synchronized (lock){
list.add("123");
System.out.println("线程A添加元素,此时list的size为:" + list.size());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(list.size() == 5){
lock.notify(); //唤醒线程B
}
}
}
});
//线程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发出notify唤醒通知后,还是走完了自己的业务后,线程B才开始执行,这里很好的验证了notify不释放锁,而wait释放锁。
三、工具类CountDownLatch
jdk1.5后新增了这个juc中的工具类,CountDownLatch基于AQS框架,不知道AQS的同学,可以去看看我这篇文章,AQS和CAS是什么?_lqkj蓝海的博客-CSDN博客,是基于一个线程间的共享变量来实现通信的。
/**
* @author lqkj
* @data 2022/10/21 10:41
* 利用JUC框架中的CountDownLatch来进行通信,采用的是线程间采用共享变量state。
*/
public class CountDownLatch_Syc {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(1);
List<String> list = new ArrayList<>();
//线程A
Thread threadA = new Thread( () ->{
for(int i = 1 ; i <= 10 ; i++){
list.add("123");
System.out.println("线程A添加元素,此时list的size为:" + list.size());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(list.size() == 5){
countDownLatch.countDown();
}
}
});
//线程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();
}
}
五、ReentranLock结合Condition
/**
* @author lqkj
* @data 2022/10/21 10:55
* 利用ReentrantLock结合Condition实现通信
* 这种方式使用起来并不是很好,
* 代码编写复杂,而且线程 B 在被 A 唤醒之后由于没有获取锁还是不能立即执行
* 也就是说,A 在唤醒操作之后,并不释放锁。这种方法跟 Object 的 wait()/notify() 一样。
*/
public class ReentrantLock_Syc {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
List<String> list = new ArrayList<>();
//线程A
Thread threadA = new Thread( () ->{
for(int i = 1 ; i <= 10 ; i++){
list.add("123");
System.out.println("线程A添加元素,此时list的size为:" + list.size());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(list.size() == 5){
condition.signal();
}
}
lock.unlock();
});
//线程B
Thread threadB = new Thread( () ->{
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();
}
}
这种方式使用起来不友好,代码编写较为复杂,这种方式和Object的wait和notify一样
六、LockSupport实现阻塞和唤醒
它是一种非常灵活的线程间的阻塞和唤醒工具,使用它的话不用关注是等待线程还是唤醒线程先运行,但是得知道线程名。
/**
* @author lqkj
* @data 2022/10/21 11:04
* 实现线程间的阻塞和唤醒
*/
public class LockSupport_Syc {
public static void main(String[] args) {
List<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("123");
System.out.println("线程A添加元素,此时list的size为:" + list.size());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(list.size() == 5){
LockSupport.unpark(threadB);
}
}
});
threadA.start();
threadB.start();
}
}
---------------------------------------------------------分割线-------------------------------------------------------------
好了,祝各位小伙伴都能找到自己满意的工作~~~