一,线程间通信介绍
概念:线程一旦开始运行,就会拥有自己独立的栈空间,那多个线程如何相互配合完成工作,这就涉及到了线程间的通信。
线程之间的通信:是使线程间能够互相发送信号,使线程能够等待其他线程的信号。比如线程 A 在执行到某个条件时,通知线程 B 再执行某个操作,我们希望多个线程协同工作来完成某个任务。
线程间通信的目的:通信的目的是为了更好的协作,线程无论是交替式执行,还是接力式执行,都需要进行通信告知。首先,要短信线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模型来实现的。我们来基本一道面试常见的题目来分析。
题目:有两个线程A、B,A线程向一个集合里面依次添加元素"abc"字符串,一共添加十次,当添加到第五次的时候,希望B线程能够收到A线程的通知,然后B线程执行相关的业务操作。
volatile关键字:基于 volatile 关键字来实现线程间相互通信是使用共享内存的思想,大致意思就是多个线程同时监听一个变量,当这个变量发生变化的时候 ,线程能够感知并执行相应的业务。这也是最简单的一种实现方式。
volatile有两大特性,一是可见性,二是有序性,禁止指令重排序,其中可见性就是可以让线程之间进行通信。
volatile语义保证线程可见性有两个原则保证
所有volatile修饰的变量一旦被某个线程更改,必须立即刷新到主内存.
所有volatile修饰的变量在使用之前必须重新读取主内存的值.
Object类的wait() 和 notify() 方法:众所周知,Object类提供了线程间通信的方法:wait()、notify()、notifyaAl(),它们是多线程通信的基础,而这种实现方式的思想自然是线程间通信。注意: wait和 notify、notifyaAl必须配合synchronized使用,wait方法释放锁,notify方法不释放锁
wait() 作用是使当前执行该代码的线程进入该对象锁的阻塞队列中进行等待(立即释放锁,进入等待锁的阻塞队列,下次被唤醒时,会接着往下执行),线程调用了对象锁的 wait 线程运行完毕以后,它会立即释放掉该对象锁。此时如果没有其他线程调用该对象锁的 notify 方法,则该调用过wait方法的线程由于没有得到该对象锁的通知,还会继续阻塞在 wait 状态,直到这个有线程调用此对象锁的 notify 或 notifyAll。
wait(long) 方法的功能是等待某一时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒。
notify() 方法作用是随机通知一个当前对象锁的阻塞队列中的线程(即调用过wait方法的线程)。notifyAll() 方法作用是唤醒当前对象锁阻塞队列中的所有线程(即调用过wait方法的线程)。
二,等待唤醒机制
概念:在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后 再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程,wait/notify 就是线程间的一种协作机制。
等待唤醒中的方法
wait:线程不再活动,不再参与调度,进入 wait set 中,释放锁资源,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从 wait set 中释放出来,重新进入到 调度队列(ready queue)中。
notify:则选取所通知对象的 wait set 中的一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先入座。
notifyAll:则释放所通知对象的 wait set 上的全部线程。
注意:
哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。
如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态。
调用wait和notify方法需要注意的细节:wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对 象调用的wait方法后的线程。wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。
三:生产者与消费者
什么是生产者消费者模式:生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
这个阻塞队列就是用来给生产者和消费者解耦的。纵观大多数设计模式,都会找一个第三者出来进行解耦,如工厂模式的第三者是工厂类,模板模式的第三者是模板类。在学习一些设计模式的过程中,如果先找到这个模式的第三者,能帮助我们快速熟悉一个设计模式。
生产者消费者模型的实现:生产者是一堆线程,消费者是另一堆线程,内存缓冲区可以使用List数组队列,数据类型只需要定义一个简单的类就好。关键是如何处理多线程之间的协作。这其实也是多线程通信的一个范例。
在这个模型中,最关键就是内存缓冲区为空的时候消费者必须等待,而内存缓冲区满的时候,生产者必须等待。其他时候可以是个动态平衡。值得注意的是多线程对临界区资源(即共享资源)的操作时候必须保证在读写中只能存在一个线程,所以需要设计锁的策略。
如下代码演示:
1:创建一个包子类,并给包子两个属性(名字 ,状态)
/**
* @author 高影 2022/12/28 21:29
* @version 1.0
* 线程的共有资源对象---包子
*/
public class BaoZi {
String name;
boolean flag;
}
2:创建两个线程1:早餐店制作包子的线程 2:吃货的线程
早餐店
/**
* @author 高影 2022/12/28 21:33
* @version 1.0
*/
public class ZaoCanDian extends Thread{
//资源对象
BaoZi baoZi;
//定义构造方法
public ZaoCanDian(String threadName,BaoZi bz){
super(threadName);
this.baoZi=bz;
}
/**
* 早餐店线程功能
* 如果包子存在,线程进入等待状态
* 如果包子不存在,线程开始制作包子,制作完毕更改包子状态为存在,唤醒吃货线程吃包子
*/
@Override
public void run(){
//获取线程的名字
String threadName=Thread.currentThread().getName();
for(int i = 0; i < 10; i++){
synchronized (baoZi){
if(baoZi.flag){//包子存在
try{
baoZi.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {//包子不存在
System.out.println(threadName+"制作"+baoZi.name);//制作包子
baoZi.flag=true; //更改包子状态
baoZi.notify(); //唤醒同一资源下的其他线程
}
}
}
}
}
吃货
/**
* @author 高影 2022/12/28 21:45
* @version 1.0
*/
public class ChiHuo extends Thread{
//资源对象
BaoZi baoZi;
//定义构造方法:给线程定义名字,同时给BaoZi对象赋值
public ChiHuo(String threadName,BaoZi bz){
super(threadName);
this.baoZi=bz;
}
/**
* 吃货线程功能
* 如果包子不存在,线程进入等待状态
* 如果包子存在,线程开始吃包子,吃完后更改包子的状态变为不存在,唤醒早餐店线程开始制作包子
*/
@Override
public void run(){
//获取线程的名字
String threadName=Thread.currentThread().getName();
for(int i = 0; i < 10; i++){
synchronized (baoZi){
if(baoZi.flag){//包子存在
System.out.println(threadName+"正在吃"+baoZi.name);
baoZi.flag=false; //更改包子状态
baoZi.notify(); //唤醒同一资源下的其他线程
}else {//包子不存在
try{
baoZi.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
测试类
/**
* @author 高影 2022/12/28 21:55
* @version 1.0
*/
public class ThreadTest01 {
public static void main(String[] args) {
BaoZi bz = new BaoZi();
bz.name="韭菜鸡蛋";
bz.flag=false;
ChiHuo ch = new ChiHuo("猪八戒",bz);
ZaoCanDian zcd = new ZaoCanDian("包小包早餐铺",bz);
ch.start();
zcd.start();
}
}
代码执行的结果