JavaEE & wait and notify & 线程状态总结
1. wait and notify
学到现在,我们已经了解到了阻塞的很多状况
阻塞的本质就是,纠正线程无序调度,让线程在某些需求场景下,有序进行
join,等待整个线程结束/等待一段时间
sleep,线程固定睡眠
synchronized,等待锁
上面的三种,各有各的好,功效有限
而wait呢,它也有它的特性
它引起的阻塞等待,就相当于给线程按了暂停键
而notify呢,则是让线程继续执行
而这个notify应该是阻塞线程外的一个线程调用的
什么时机按下启动键,看具体需要~
而这两个方法进行配合,可以更精细的控制线程调度顺序~
根据 【适用场景:::我们的初心】 去选择阻塞方式~
1.1 应用场景
假设你今天要去银行取钱,ATM没钱了
而其他人都有钱
假设那么ATM是个锁对象
那么你跟其他人一起抢这个锁去操作
而你是要取钱的,如果没人存钱或者没有工作人员运钞填入钱,你就不能取到钱
尽管你抢到锁也没用~
那么,如果你在一个很短的时间内,反复抢到锁,那么是不是就是在浪费时间
这个时间很短,线程的记账信息还没起作用~
而接下来,我们就是为了解决这个问题~
1.2 wait与notify配合解决问题
基本思想就是
你抢到ATM机了,你取不到钱,你就wait,直到别人来notify你说,你可以去取钱了~
1.2.1 wait和notify的使用
wait和notify都是Object类的方法,只要不是基本数据类型的变量,都可以调用~
谁调用wait,则对应该线程就会阻塞等待
谁调用notify,无所谓,随机唤醒一个WAITING的线程
简简单单的调用是会报错的~
这里也体现了wait的主要要做的三件事:
释放锁
阻塞等待
等待通知唤醒,唤醒后继续参与“抢锁”
补充: notify能够唤醒的线程,必须和notify的锁对象是一样的
notify要依据这个锁去唤醒线程~
没有抢到锁,就notify一样会报错
并且notify也有一步操作是,释放锁,否则被唤醒的线程和其他线程无法去抢夺锁
1.2.2 wait和notify代码规范
必须先wait再notify才有效果,否则就没有效果
将相当于你取得到钱,你也没wait阻塞等待,就有人来notify你说,可以取钱去了,你会觉得莫名其妙,但是对你没有什么影响
public class Test { public static void main(String[] args) throws InterruptedException { Object object = new Object(); Thread thread1 = new Thread(() -> { while(true) { System.out.println("wait 前"); synchronized (object) { try { object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("wait 后"); } }); Thread thread2 = new Thread(() -> { System.out.println("notify 开始"); synchronized (object) { object.notify(); } System.out.println("notify 结束"); }); thread1.start(); thread2.start(); } }
测试结果:
顺序正常~
这就是通过具体情况,调整cpu调度顺序的方式~
很多方法都能引起这种效果,但是初心不同,所以,要看具体想法,使用特定的方式~
1.2.3 补充
wait还有一个重载方法,提供了带参数的方法
这个参数代表了,最长等待时间,超过该时间,就自动唤醒~
notify还有一个哥哥,notifyAll
他可以根据自己的锁对象,去唤醒所有对应的线程~
而notify是随机唤醒多个中的一个~
sleep,join好像也能被中断唤醒,也有时间限制,这样就跟wait差不多了咯~
没错,这个场景差不多,但是他们的初心不一样
在不同场景用更加有针对性的方式,才是好决策~
目前的场景只是为了体现wait和notify的使用方式
你可能没能体会到,他们的配合的在这里对任务的针对性~
并且,锁代码块也并不是只有那么个语句~
以后写的应该是特定情况下会wait~
没有关系,后面我会写几篇博客,研究 Java多线程的一些案例
综合线程知识去研究
结合具体场景运用不同方式
2. 线程状态总结
在之前的《进程与线程》博客里给出这张图,由于部分知识未学习,现在重新看一遍~
接下来,我将讲解各个状态,并且用代码去测试
2.1 NEW
NEW 线程还没被创建出来,只是存在这个线程对象~
start 创建 + 启动
也就是说start之前~
public class TestState { public static void main(String[] args) { Thread thread1 = new Thread(() -> { System.out.println("好耶 ^ V ^"); }); System.out.println(thread1.getState()); thread1.start(); } }
2.2 RUNNABLE
RUNNABLE 运行/就绪状态
正在CPU上运行准备好
随时可以去CPU运行
这两种情况,基本“同时”
因为线程并发执行的原因
所以一个线程总是处于运行和就绪的状态~
但是这一个很小的时间内,并不是阻塞~
public class TestState { public static void main(String[] args) { Thread thread1 = new Thread(() -> { System.out.println("好耶 ^ V ^"); }); thread1.start(); System.out.println(thread1.getState()); } }
2.3 TERMINATED
TERMINATED 系统中的线程已经执行完毕~
但是线程引用还在~
通过这个“还在的”引用去查看状态
由于线程调度的随机性,所以2.2代码是可能出现TERMINATED状态的,如下图:
但是概率低~
所以我让main线程等一会儿~
public class TestState { public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(() -> { System.out.println("好耶 ^ V ^"); }); thread1.start(); Thread.sleep(10); System.out.println(thread1.getState()); } }
2.4 TIMED_WAITING
TIMED_WAITING
被指定时间的等待—sleep
对于join方法
即使正在等待的线程是完全阻塞的状态
我们可以通过全局性质的静态变量去获得线程引用并在lambda表达式中被捕获到~
如果有时间限制就是TIMED_WAITING~
否则是WAITING~
public class TestState { public static Thread t; public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(() -> { System.out.println("好耶 ^ V ^"); System.out.println(t.getState()); }); thread1.start(); t = Thread.currentThread(); thread1.join(1000); } }
public class TestState { public static Thread t; public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(() -> { System.out.println("好耶 ^ V ^"); System.out.println(t.getState()); }); thread1.start(); t = Thread.currentThread(); Thread.sleep(1000); } }
public class TestState { public static Thread t; public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(() -> { System.out.println("好耶 ^ V ^"); System.out.println(t.getState()); }); thread1.start(); t = Thread.currentThread(); thread1.join(); } }
2.5 BLOCKED
BLOCKED
表示等待锁出现的状态
class Counter { private int count = 0; public void add() { synchronized (Counter.class) { count++; } } public int get() { return count; } }
public class TestState { public static Thread t; public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); Thread thread1 = new Thread(() -> { for (int i = 0; i < 5000; i++) { synchronized (counter) { counter.add(); System.out.println(t.getState()); } } }); thread1.start(); t = Thread.currentThread(); for (int i = 0; i < 5000; i++) { synchronized (counter) { counter.add(); } } } }
而main线程可能很快就会执行完,所以就没必要阻塞等待了~
2.6 WAITING
主要是因为wait方法
join()不带参数版本,是等待整个线程结束
很直接
下面在线程2notify前后分别去查看线程1的状态~
public class TestState { public static void main(String[] args) throws InterruptedException { Object object = new Object(); Thread thread1 = new Thread(() -> { while(true) { System.out.println("wait 前"); synchronized (object) { try { object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("wait 后"); } }); Thread thread2 = new Thread(() -> { System.out.println("notify 开始"); System.out.println(thread1.getState()); synchronized (object) { System.out.println(thread1.getState()); object.notify(); } System.out.println("notify 结束"); System.out.println(thread1.getState()); }); thread1.start(); thread2.start(); } }