多线程基础3:线程状态,notify,wait,join
线程的5种状态
线程共包括以下5种状态(java中定义的)
1. 新建状态(New) :
线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
2. 就绪状态(Runnable):
也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。
例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3. 运行状态(Running) :
线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
4. 阻塞状态(Blocked) :
阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(01) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
(02) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
(03) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。
当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5. 死亡状态(Dead) :
线程执行完了或者因异常退出了run()方法,该线程结束生命周期。



当线程执行 wait()方法之后,线程进入 WAITING(等待) 状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,
而 TIME_WAITING(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 sleep(long millis)方法或 wait(long millis)方法可以将 Java 线程置于 TIMED WAITING 状态。
当超时时间到达后 Java 线程将会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 BLOCKED(阻塞) 状态。
线程在执行 Runnable 的run()方法之后将会进入到 TERMINATED(终止) 状态。
线程的等待与唤醒 notify与wait
Object类中关于等待/唤醒的API详细信息如下:
- notify()-- 唤醒在此对象监视器上等待的单个线程。
- notifyAll() -- 唤醒在此对象监视器上等待的所有线程。
- wait() -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,
当前线程被唤醒(进入“就绪状态”)。
wait(long timeout)-- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout, int nanos) -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法
或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。
package com.example;
public class ThreadA extends Thread {
public ThreadA(String name){
super(name);
}
@Override
public void run(){
synchronized (this){//“线程t1”运行之后,通过synchronized(this)获取“当前对象的锁”
System.out.println(Thread.currentThread().getName()+" call notify");
notify();// // 唤醒当前的wait线程
}
}//“线程t1”运行完毕之后,释放“当前对象的锁”。紧接着,“主线程”获取“t1对象的锁”,然后接着运行。
}
package com.example;
public class WaitTest {
public static void main(String[] args) throws InterruptedException{
ThreadA t1=new ThreadA("t1");
synchronized (t1){//随后通过synchronized(t1)获取“t1对象的同步锁”
System.out.println(Thread.currentThread().getName()+" t1 start ");
t1.start();
System.out.println(Thread.currentThread().getName()+" wait");
//“主线程”执行t1.wait() 释放“t1对象的锁”并且进入“等待(阻塞)状态”。
// 等待t1对象上的线程通过notify() 或 notifyAll()将其唤醒。
t1.wait();// 主线程等待t1通过notify()唤醒。
System.out.println(Thread.currentThread().getName()+" continue");
}
}
}

对于上面的代码?曾经有个朋友问到过:t1.wait()应该是让“线程t1”等待;但是,为什么却是让“主线程main”等待了呢?
在解答该问题前,我们先看看jdk文档中关于wait的一段介绍:
中文意思大概是:
引起“当前线程”等待,直到另外一个线程调用notify()或notifyAll()唤醒该线程。
换句话说,这个方法和wait(0)的效果一样!(补充,对于wait(long millis)方法,当millis为0时,表示无限等待,直到被notify()或notifyAll()唤醒)。
“当前线程”在调用wait()时,必须拥有该对象的同步锁。
该线程调用wait()之后,会释放该锁;
然后一直等待直到“其它线程”调用对象的同步锁的notify()或notifyAll()方法。
然后,该线程继续等待直到它重新获取“该对象的同步锁”,然后就可以接着运行。
注意:jdk的解释中,说wait()的作用是让“当前线程”等待,而“当前线程”是指正在cpu上运行的线程!
这也意味着,虽然t1.wait()是通过“线程t1”调用的wait()方法,但是调用t1.wait()的地方是在“主线程main”中。
而主线程必须是“当前线程”,也就是运行状态,才可以执行t1.wait()。
所以,此时的“当前线程”是“主线程main”!因此,t1.wait()是让“主线程”等待,而不是“线程t1”!
wait(long timeout)和notify()
package com.example;
public class ThreadA extends Thread {
public ThreadA(String name){
super(name);
}
@Override
public void run(){
System.out.println(Thread.currentThread().getName()+" run ");
while (true)//“线程t1”运行之后,进入了死循环,一直不断的运行
;
}
}
package com.example;
public class WaitTest {
public static void main(String[] args) throws InterruptedException{
ThreadA t1=new ThreadA("t1");
synchronized (t1){
System.out.println(Thread.currentThread().getName()+" t1 start ");
t1.start();
System.out.println(Thread.currentThread().getName()+" wait");
// 主线程进入“阻塞状态”。需要“用于t1对象锁的线程通过notify() 或者 notifyAll()将其唤醒” 或者
// “超时3000ms之后”,主线程main才进入到“就绪状态”,然后才可以运行。
t1.wait(3000);
// 超时3000ms之后,主线程main会进入到“就绪状态”,然后接着进入“运行状态”。
System.out.println(Thread.currentThread().getName()+" continue");
}
}
}
结果:
main t1 start
main wait
t1 run //等待3000ms后运行
main continue

wait() 和 notifyAll()
package com.example;
import sun.awt.windows.ThemeReader;
public class NotifyAllTest {
private static Object obj=new Object();
public static void main(String[] args) {
//主线程中新建并且启动了3个线程"t1", "t2"和"t3"。
ThreadA t1=new ThreadA("t1");
ThreadA t2=new ThreadA("t2");
ThreadA t3=new ThreadA("t3");
t1.start();
t2.start();
t3.start();
try {
System.out.println(Thread.currentThread().getName()+" sleep 3000");
Thread.sleep(3000);
}catch (InterruptedException ex){
ex.printStackTrace();
}
synchronized (obj){
//主线程休眠3秒之后,接着运行。执行 obj.notifyAll() 唤醒obj上的等待线程,即唤醒"t1", "t2"和"t3"这3个线程。
// 紧接着,主线程的synchronized(obj)运行完毕之后,主线程释放“obj锁”。
// 这样,"t1", "t2"和"t3"就可以获取“obj锁”而继续运行了
System.out.println(Thread.currentThread().getName()+" notify all");
obj.notifyAll();
}
}
static class ThreadA extends Thread{
public ThreadA(String name){
super(name);
}
public void run(){
synchronized (obj){
try {
//主线程通过sleep(3000)休眠3秒。在主线程休眠3秒的过程中,我们假设"t1", "t2"和"t3"这3个线程都运行了。
// 以"t1"为例,当它运行的时候,它会执行obj.wait()等待其它线程通过notify()或额nofityAll()来唤醒它;
// 相同的道理,"t2"和"t3"也会等待其它线程通过nofity()或nofityAll()来唤醒它们。
System.out.println(Thread.currentThread().getName()+" wait");
obj.wait();
System.out.println(Thread.currentThread().getName()+" continue");
}catch (InterruptedException ex){
ex.printStackTrace();
}
}
}
}
}
运行结果
main sleep 3000
t1 wait
t2 wait
t3 wait
main notify all
t3 continue
t2 continue
t1 continue

为什么notify(), wait()等函数定义在Object中,而不是Thread中
Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。
wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有的“同步锁”,
否则其它线程获取不到该“同步锁”而无法运行!OK,线程调用wait()之后,会释放它锁持有的“同步锁”;
而且,根据前面的介绍,我们知道:等待线程可以被notify()或notifyAll()唤醒。
现在,请思考一个问题:notify()是依据什么唤醒等待线程的?
或者说,wait()等待线程和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。
负责唤醒等待线程的那个线程(我们称为“唤醒线程”),
它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),
并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。
虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。
必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。
总之,notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个!
这就是为什么notify(), wait()等函数定义在Object类,而不是Thread类中的原因。
join
join() 定义在Thread.java中。
join() 的作用:让“主线程”等待“子线程”结束之后才能继续运行。
主线程与子线程概念
// 主线程
public class Father extends Thread {
public void run() {
Son s = new Son();
s.start();
s.join();
...
}
}
// 子线程
public class Son extends Thread {
public void run() {
...
}
}
两个类Father(主线程类)和Son(子线程类)。因为Son是在Father中创建并启动的,所以,Father是主线程类,Son是子线程类。
在Father主线程中,通过new Son()新建“子线程s”。
接着通过s.start()启动“子线程s”,并且调用s.join()。
在调用s.join()之后,Father主线程会一直等待,直到“子线程s”运行完毕;
在“子线程s”运行完毕之后,Father主线程才能接着运行。
这也就是我们所说的“join()的作用,是让主线程会等待子线程结束之后才能继续运行”!
join的JDK源码
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
wait()的作用是让“当前线程”等待,而这里的“当前线程”是指当前在CPU上运行的线程。
所以,虽然是调用子线程的wait()方法,但是它是通过“主线程”去调用的;
所以,休眠的是主线程,而不是“子线程”!
package com.example;
public class ThreadA extends Thread {
public ThreadA(String name){
super(name);
}
@Override
synchronized public void run(){
for (int i=0;i<10;i++){
System.out.printf("%s [%d]:%d\n", this.getName(), this.getPriority(), i);
}
}
}
package com.example;
pulic class Test {
public static void main(String[] args)throws InterruptedException {
ThreadA t1=new ThreadA("t1");
t1.start();
t1.join();
System.out.printf("%s finish\n", Thread.currentThread().getName());
}
}
(01) 在“主线程main”中通过 new ThreadA("t1") 新建“线程t1”。 接着,通过 t1.start() 启动“线程t1”,并执行t1.join()。
(02) 执行t1.join()之后,“主线程main”会进入“阻塞状态”等待t1运行结束。“子线程t1”结束之后,会唤醒“主线程main”,“主线程”重新获取cpu执行权,继续运行

sleep() 方法和 wait() 方法区别和共同点
相同点:
两者都可以暂停线程的执行。
不同点:
两者最主要的区别在于:sleep 方法没有释放锁,而 wait 方法释放了锁 。
应用场景: Wait 通常被用于线程间交互/通信,sleep 通常被用于暂停执行。
wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法。
sleep() 方法执行完成后,线程会自动苏醒。或者可以使用wait(long timeout)超时后线程会自动苏醒。
参考文章


浙公网安备 33010602011771号