声明:该专栏本人重新过一遍java知识点时候的笔记汇总,主要是每天的知识点+题解,算是让自己巩固复习,也希望能给初学的朋友们一点帮助,大佬们不喜勿喷(抱拳了老铁!)
往期回顾
Java学习day25:守护线程、死锁、线程生命周期(知识点详解)-CSDN博客
Java学习day24:线程的同步和锁(例题+知识点详解)-CSDN博客
Java学习day23:线程构造方法、常用方法(例题+知识点详解)-CSDN博客
Java学习day26:和线程相关的Object类下面的方法、等待线程和唤醒线程
一、和线程相关的Object类的方法
1.三个常用方法
void wait() | 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法。 |
void notify() | 唤醒正在等待对象监视器的单个线程。 |
void notifyAll() | 唤醒正在等待对象监视器的所有线程。 |
wait() 方法换句话说,这个方法的行为就好像简单地执行呼叫`wait(0)`
二、等待线程和唤醒线程
1.什么是等待线程和唤醒线程
上面我们看了,Object类的三个主要方法,这三个主要方法就涉及到等待线程和唤醒线程,也就是说,至少两个线程,其中一个线程中使用对象.wait() 那么这个线程就会阻塞,代码不会往下执行了。也就是我们说的,等待线程。如何想让这个线程往下执行呢?再开另外一个线程,使用对象.notify()去唤醒另外那个等待线程。也就是我们说的唤醒线程,如果多个等待线程,一个唤醒线程,则调用notifyAll()方法,就能一次性唤醒所有等待线程。
我们用代码来理解
示例:
//创建这个类的目的,就是实例化出来对象,然后拿这个对象
//调用wait方法和notify方法
//wait方法和notify方法是object对象的方法
class Message {
private String message;
public Message(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String toString() {
return "Message{" +
"message='" + message + '\'' +
'}';
}
}
//导致当前线程等待,直到另一个线程调用该对象的[`notify()`
class WaiterThread implements Runnable {
//想一个问题?WaiterThread 使用message对象调用
//wait() 咋解决?
private Message msg;
public WaiterThread(Message msg) {
this.msg = msg;
}
@Override
public void run() {
//先获取当前线程名字
String name = Thread.currentThread().getName();
System.out.println(name + "等待唤醒时间:" +System.currentTimeMillis());
//让等待线程去阻塞,去等待 这个线程执行不下去了
//锁的是msg对象
synchronized (msg) {//为啥用这个wait的时候要加锁?等会讲
try {
msg.wait();//代码走到这,当前这个线程阻塞,不往下走了
//咱们得想办法让这个等待线程继续执行下去,咋办?
//在另外一个线程中去调用notify方法那么等待线程就会执行下去
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("123");
System.out.println(name + "被唤醒的时间:" + System.currentTimeMillis());
}
}
}
//唤醒线程
class NotifyThread implements Runnable {
//也要用同一个对象是WaiterThread线程中同一个对象调用notify()方法
private Message msg;
public NotifyThread(Message msg) {
this.msg = msg;
}
@Override
public void run() {
try {
//我的想法是不能先让唤醒线程执行
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "开始唤醒等待线程");
synchronized (msg) {
msg.setMessage("我是修改之后的message值");
msg.notify();
//msg.notifyAll();//唤醒所有线程
}
}
}
public class Demo1 {
public static void main(String[] args) {
Message message = new Message("我是message属性");
WaiterThread waiterThread = new WaiterThread(message);
NotifyThread notifyThread = new NotifyThread(message);
//如果等待线程好几个 咋办呢?
new Thread(waiterThread, "等待线程1").start();
// new Thread(waiterThread, "等待线程2").start();
//new Thread(waiterThread, "等待线程3").start();
new Thread(notifyThread, "唤醒线程").start();
}
}
代码执行结果:
//等待线程等待唤醒时间:1660187660718 等待线程
//唤醒线程开始唤醒等待线程 唤醒线程
//123 等待线程
//等待线程被唤醒的时间:1660187661740 等待线程
//这叫线程之间的通信问题!!!
理解这段代码核心:
先是写一个message方法用于信息显示,这个没啥好说的,然后写了两个类用来创建两个线程,分别是等待线程和唤醒线程,等待线程执行wait方法后就相当于不再执行接下来的代码,进入就绪等待状态,让唤醒线程获得cpu使用权,唤醒线程调用notify()方法后,等待线程再继续执行。同时代码里已经写清楚了,如果有多个等待线程,则唤醒线程的对象需要调用notifyAll()方法,一次性唤醒所有等待线程,而且我们必须明确的是,哪个等待线程先被唤醒是不知道的,因为抢占式运行,哪个线程先抢到cpu执行权,唤醒线程就先唤醒哪个线程。
总结:
新建两个线程:
一个是等待线程
线程里面的代码从上往下执行的,但是使用object.wait(),就这个方法一用,你的线程就
阻塞了,就处于等待状态。意味着当前的代码到了wait方法以后的代码暂时不执行了
另外一个是唤醒线程。
唤醒线程中使用object.notify()方法,这个方法是专门唤醒刚才那个等待线程。让等待线程继续执行
同时,有一个很重要的点:调用wait()的等待线程必须加锁,具体为什么?
wait需要持有锁的原因是,你肯定需要知道在哪个对象上进行等待,如果不持有锁,将无法做到对象变更时进行实时感知通知的作用。与此同时,为了让其他线程可以操作该值的变化,它必须要先释放掉锁,然后在该节点上进行等待。不持有锁而进行wait,可能会导致长眠不起。而且,如果不持有锁,则当wait之后的操作,都可能是错的,因为可能这个数据已经过时,其实也叫线程不安全了。总之,一切为了安全,单独的wait做不成这事。
通俗来说就是,因为我要知道对哪个对象进行唤醒的!!!
举个生活中的例子:
大学的时候在楼底下等过自己的女朋友。
你就是等待线程
你女朋友就是唤醒线程
你在楼底下等的时候 就是在wait。
你女朋友下楼之后,唤醒你 咱们走吧。
但是你女朋友 去唤醒别人的男朋友?这就扯犊子了,所有的加锁是为了保证是同一个对象的
所以说,我们必须要对等待线程和唤醒线程都加锁。
以上,就是今天的所有知识点了。等待线程和唤醒线程的代码大家要多花点时间理解,方法其实就三个,但是大家要知道怎么用的,有些代码细节都没讲到,大家要自己多花点时间,静下心看代码,写代码,多理解,多运用,重点是多去运用。
加油吧,预祝大家变得更强!