线程之间为什么需要进行通信?
我们都知道,要想能够去执行一个线程,首先这个线程需要获取CPU的执行权,当这个线程执行完毕之后,就会释放CPU资源,并发执行的时候,剩下的处于就绪状态的线程就会一起去争夺CPU的执行权,谁抢到谁就执行。但是在开发过程中,我们可能需要多个线程进行协调配合来完成一件事。也就是说,我们需要线程之间有规律的去执行任务。这就需要线程之间相互通信了。
当多个线程共同去争夺同一个cpu资源的时候,只能有一个线程能够抢到,其他没抢到的就会进入阻塞状态
,等待抢到cpu资源的线程执行结束再去抢夺。通过一个简单的例子说明一下。包子店老板A将包子生产好之后,就叫顾客B过来吃。这个小案例中,就提现了类似线程中等待与唤醒机制。
A先生产,此时B在等待A生产好。
A生产好之后,通知B来吃,相当于B被A唤醒。
线程之间的通信依靠的是wait()
、notify()
、notifyAll()
方法进行协调。这三个方法都是定义在了Object
类中
等待与唤醒方法基本概念:
wait():
当线程A调用wait()方法后,释放同步锁,进入阻塞状态,然后加入到等待锁对象的队列中。
notify():
线程B获取到同步锁之后,调用notify()方法,从等待锁的队列中唤醒一个线程(被唤醒的线程状态由等待转变成就绪,等线程B执行完毕释放了锁资源之后,被唤醒的线程获取到锁之后就会去执行该线程)
notifyAll():
线程B获取到同步锁之后,调用notifyAll()方法,会唤醒等待锁队列中所有的线程,等待线程B执行完之后释放锁资源,被唤醒的线程去争夺锁资源,获取到锁对象的线程会去执行相应的逻辑。
使用wait和notify方法时,需要注意这些细节:
wait方法与notify方法必须要由同一个锁对象调用。对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法线程。
wait方法与notify方法是属于Object类的方法,锁对象可以是任意对象,而任意对象的所属类都是继承Object类的。
wait方法与notify方法必须要在同步代码块或者同步函数中使用,必须通过锁对象调用这两个方法。关于线程间的通信和等待唤醒机制,其实,两者没有很大的区别,总结成一句话,线程之间的分工合作,即哪个线程,什么时候该等待。哪个线程,什么时候该执行,以保证资源的有效利用。
练习:生产者和消费者案例
包子代码:
public class BaoZi {
String name;//包子的名称
Boolean flag;//包子的状态(true表示存在 false 表示不存在)
}
吃货代码:
public class ChiHuo extends Thread{
BaoZi baoZi;
//构造函数:用来指定线程的名称和操作资源
public ChiHuo(String name,BaoZi baoZi){
super(name);
this.baoZi=baoZi;
}
public void run(){
String threadName=Thread.currentThread().getName();
int count=0;
while(true){
synchronized (baoZi){
count++;
if (count>10){
break;
}
if(baoZi.flag){//如果包子存在
System.out.println(threadName+"开始吃"+baoZi.name);
baoZi.flag=false;//修改状态
baoZi.notify();//唤醒其他资源状态
}else{
//如果包子不存在
try {
baoZi.wait();//进入等待状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
包子店代码:
public class ZaoCanDian extends Thread{
BaoZi baoZi;
//构造函数:用来指定线程的名称和操作资源
public ZaoCanDian(String name, BaoZi baoZi) {
super(name);
this.baoZi = baoZi;
}
public void run() {
String threadName = Thread.currentThread().getName();
int count = 0;
while (true) {
synchronized (baoZi) {
count++;
if (count > 10) {
break;
}
if (baoZi.flag) {//如果包子存在
try {
baoZi.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
//如果包子不存在
System.out.println(threadName + "开始制作" + baoZi.name);
baoZi.flag=true;//更改包子状态
baoZi.notify();//唤醒同一资源下的其他线程
}
}
}
}
}
测试类:
public class ThreadTest {
public static void main(String[] args) {
//定义资源对象
BaoZi baoZi=new BaoZi();
baoZi.name="韭菜鸡蛋";
baoZi.flag=true;
//定义两个线程,起名字且操作同一对象
ChiHuo ch=new ChiHuo("猪八戒",baoZi);
ZaoCanDian zcd=new ZaoCanDian("春光早餐",baoZi);
//启动线程
zcd.start();
ch.start();
}
}