文章目录
4. 多线程线程之间的通讯
4.1 wait/notify机制
等待/通知的相关方法是任意Java对象都具备的,因为这些方法被定义在所有对象的超类java.lang.Object上,方法如下:
-
notify() :通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获取到了对象的锁
-
notifyAll():通知所有等待在该对象的线程
-
wait():调用该方法的线程进入WAITING状态,只有等待其他线程的通知或者被中断,才会返回。需要注意调用wait()方法后,会释放对象的锁 。
注意: wait,notify和notifyAll要与synchronized一起使用 否则将会报错
synchronized作用的对象可以是任意一个, 因此wait/notify/notifyAll都是Object的方法
4.1.1 wait/notify/notifyall基本使用
public class MyThread extends Thread {
@Override
public void run() {
//上锁对象为myThread
synchronized (this){
try {
System.out.println("myThread对象调用wait方法,myThread线程进入WAITING状态,并释放myThread对象锁");
this.wait();
System.out.println("wait()已返回");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (myThread){
System.out.println("3秒后唤醒在myThread对象上等待的myThread线程");
myThread.notify();
}
}
}
4.1.2 多线程通讯实现生产者消费者模型
public class MyModelTest {
class MyResource {
public String name;
public int age;
//是否能消费标记
public boolean flag = false;
}
//消费者线程类
class ConsumerThread extends Thread{
private MyResource myResource;
ConsumerThread(MyResource myResource){
this.myResource = myResource;
}
@Override
public void run() {
while (true){
synchronized (myResource){
//不能消费则等待
if (!myResource.flag){
try {
myResource.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//进行消费
System.out.println("消费资源: name="+ myResource.name + " age="+myResource.age);
//完成消费
myResource.flag = false;
myResource.notify();
}
}
}
}
//生产者线程类
class ProviderThread extends Thread{
private MyResource myResource;
ProviderThread (MyResource myResource){
this.myResource = myResource;
}
@Override
public void run() {
int count = 0;
while (true) {
synchronized (myResource) {
//不能生产则等待
if (myResource.flag){
try {
myResource.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//进行生产
if (count % 2 == 0){
myResource.name = "张三";
myResource.age = 15;
}else {
myResource.name = "李四";
myResource.age = 28;
}
//完成生产
myResource.flag = true;
myResource.notify();
}
count = (count + 1) % 2;
}
}
}
public static void main(String[] args) {
new MyModelTest().start();
}
public void start(){
MyResource myResource = new MyResource();
ProviderThread providerThread = new ProviderThread(myResource);
ConsumerThread consumerThread = new ConsumerThread(myResource);
providerThread.start();
consumerThread.start();
}
}
4.2 join机制
现有两个线程A、B,在线程A中调用 B.join() 使得线程A在等待线程B执行完以后再执行
4.2.1 join/Wait/sleep之间的区别
join(long)方法先执行另外的一个线程,在等待的过程中释放对象锁 底层是基于wait封装的
sleep(long)方法在睡眠时不释放对象锁
Wait(long)方法在等待的过程中释放对象锁
4.2.2 join基本使用
public class JoinThread {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(3000);
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() + ",线程执行");
}, "t1");
Thread t2 = new Thread(() -> {
try {
t1.join();
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + ",线程执行");
}, "t2");
Thread t3 = new Thread(() -> {
try {
t2.join();
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + ",线程执行");
}, "t3");
t1.start();
t2.start();
t3.start();
}
}
4.2.3 join的底层原理
Join底层原理是基于wait封装的,其方法上标有synchronized锁,也就是说调用t1线程join方法的t2线程会在t1线程对象上进行等待,但唤醒的代码在jvm Hotspot 源码中
当jvm在关闭线程之前会检测线阻塞在t1线程对象上的线程,然后执行notfyAll(),这样t2就被唤醒了。