1. 线程状态图
这是线程的7状态模型图,常见的7大状态之间的转换关系也在上面。
多线程之间的通信主要用到4个方法,wait()、wait(long time)、notify()、notifyAll()。
-
wait():作用是使当前线程从调用处中断并且释放锁转入等待队列,直到收到notify或者notifyAll的通知才能从等待队列转入锁池队列,没有收到停止会一直死等。
-
wait(long time):相比wait多了一个等待的时间time,如果经过time(毫秒)时间后没有收到notify或者notifyAll的通知,自动从等待队列转入锁池队列。
-
notify():随机从等待队列中通知一个持有相同锁的一个线程,如果没有持有相同锁的wait线程那么指令忽略无效。注意是持有相同锁,并且是随机没有固定的,顺序这一点在生产者消费者模型中很重要,会造成假死的状态。
-
notifyAll():通知等待队列中的持有相同锁的所有线程,让这些线程转入锁池队列。如果没有持有相同锁的wait线程那么指令忽略无效。
-
wait的两个方法都需要注意中断的问题,wait中断是从语句处中断并且释放锁,当再次获得锁时是从中断处继续向下执行。
-
notify 和 notifyAll方法通知是延迟通知,必须等待当前线程体执行完所有的同步方法/代码块中的语句退出释放锁才通知wait线程。
-
这四个方法都必须在获得锁的情况下才能调用,否则会出现非法监视状态异常。
2. 基本使用
2.1、wait立即释放锁 与 notify/notifyAll延迟通知
package com.study;
import java.time.LocalDateTime;
/**
* wait与notify/notifyAll延迟通知
*/
public class Demo {
public static void main(String[] args) throws Exception {
MyService myService = new MyService();
WaitThread waitThread = new WaitThread("WaitThread", myService);
waitThread.start();
Thread.sleep(1000);
NotifyThread notifyThread = new NotifyThread("NotifyThread", myService);
notifyThread.start();
}
}
class MyService {
private Object obj = new Object();
public void waitfun() {
try {
synchronized (obj) {
System.out.println("begin wait: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
obj.wait();
System.out.println("end wait: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void notifyfun() {
try {
synchronized (obj) {
System.out.println("begin notify: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
obj.notify();
System.out.println("end notity: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class WaitThread extends Thread {
private MyService myService;
public WaitThread(String name, MyService myService) {
super(name);
this.myService = myService;
}
@Override
public void run() {
myService.waitfun();
}
}
class NotifyThread extends Thread {
private MyService myService;
public NotifyThread(String name, MyService myService) {
super(name);
this.myService = myService;
}
@Override
public void run() {
myService.notifyfun();
}
}
分析:
WaitThread线程要先获得对象锁,才能进入代码块,然后调用wait方法进行中断(类似阻塞等待),并立即释放对象锁;
main线程睡眠1000ms后,
NotifyThread线程要先获得对象锁,才能进入代码块,然后调用执行notify通知方法,这个时候上面的wait方法并不会立即往下执行,因为这个时候上面的WaitThread线程还没有获得对象锁,
等NotifyThread线程执行完代码块释放锁后,WaitThread线程获得对象锁后,wait方法不再阻塞,继续往下执行。
执行完了NotifyThread线程 同步方法/代码块里的所有语句 才进行通知,WaitThread线程收到通知进行执行,从中断处继续向下执行最后得到上图结果。
2.2、wait(long time)的自动唤醒
package com.study;
import java.time.LocalDateTime;
/**
* wait与notify/notifyAll延迟通知
*/
public class Demo {
public static void main(String[] args) throws Exception {
MyService myService = new MyService();
WaitThread waitThread = new WaitThread("WaitThread", myService);
waitThread.start();
Thread.sleep(1000);
NotifyThread notifyThread = new NotifyThread("NotifyThread", myService);
notifyThread.start();
}
}
class MyService {
private Object obj = new Object();
public void waitfun() {
try {
synchronized (MyService.class) {
System.out.println("begin wait: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
MyService.class.wait(5000);
System.out.println("end wait: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
}
System.out.println(Thread.currentThread().getName()+"等待5000ms后自动唤醒!");
} catch (Exception e) {
e.printStackTrace();
}
}
public void notifyfun() {
try {
synchronized (MyService.class) {
System.out.println("begin notify: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
// MyService.class.notify();
System.out.println("end notity: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
Thread.sleep(10000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class WaitThread extends Thread {
private MyService myService;
public WaitThread(String name, MyService myService) {
super(name);
this.myService = myService;
}
@Override
public void run() {
myService.waitfun();
}
}
class NotifyThread extends Thread {
private MyService myService;
public NotifyThread(String name, MyService myService) {
super(name);
this.myService = myService;
}
@Override
public void run() {
myService.notifyfun();
}
}
方法主体中并没有对wait的线程进行唤醒停止,但是经过time秒后WaitThread线程自动唤醒了。wait(long time)方法在经过time(毫秒)之后的等待即使没有收到 通知 会自动从等待队列转入锁池队列,由于NotifyThread线程10秒后才执行完代码块才释放锁,虽然WaitThread在5秒后就有竞争锁的资格,但是获得锁是在10秒后,所以10秒后wait(long time)方法才唤醒,往下继续执行。
2.3、提前唤醒
处于wait(long time)的线程如果正处在time等待的时间内提前收到通知会直接进入锁池队列,time时间提前失效。
package com.study;
import java.time.LocalDateTime;
/**
* wait与notify/notifyAll延迟通知
*/
public class Demo {
public static void main(String[] args) throws Exception {
MyService myService = new MyService();
WaitThread waitThread = new WaitThread("WaitThread", myService);
waitThread.start();
Thread.sleep(1000);
NotifyThread notifyThread = new NotifyThread("NotifyThread", myService);
notifyThread.start();
}
}
class MyService {
private Object obj = new Object();
public void waitfun() {
try {
synchronized (MyService.class) {
System.out.println("begin wait: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
MyService.class.wait(8000);
System.out.println("end wait: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void notifyfun() {
try {
synchronized (MyService.class) {
System.out.println("begin notify: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
MyService.class.notify();
System.out.println("end notity: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class WaitThread extends Thread {
private MyService myService;
public WaitThread(String name, MyService myService) {
super(name);
this.myService = myService;
}
@Override
public void run() {
myService.waitfun();
}
}
class NotifyThread extends Thread {
private MyService myService;
public NotifyThread(String name, MyService myService) {
super(name);
this.myService = myService;
}
@Override
public void run() {
myService.notifyfun();
}
}
虽然wait(8000);在8秒后才会自动唤醒,但是notify可以提前唤醒,只要竞争到锁,wait(8000)方法立即往下继续执行。
2.5、单一通知
notify方法随机的从等待队列中唤醒一个持有相同锁的线程进入锁池队列争抢同步锁,如果没有持有相同锁的wait的线程则notify指令无效。
package com.study;
import java.time.LocalDateTime;
/**
* wait与notify/notifyAll延迟通知
*/
public class Demo {
public static void main(String[] args) throws Exception {
MyService myService = new MyService();
for (int i = 1; i <= 5; i++) {
new WaitThread("WaitThread" + i, myService).start();
}
Thread.sleep(1000);
for (int i = 6; i <= 10; i++) {
new NotifyThread("NotifyThread" + i, myService).start();
}
}
}
class MyService {
private Object obj = new Object();
private Object obj2 = new Object();
public void waitfun() {
try {
synchronized (obj) {
System.out.println("begin wait: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
obj.wait();
System.out.println("end wait: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void notifyfun() {
try {
// 0 ≤ Math.random() < 1
if (Math.random() < 0.7) {//70%的概率
synchronized (obj) {
System.out.println("begin notify: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
obj.notify();
System.out.println("end notity: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
Thread.sleep(1000);
}
}else{
synchronized (obj2) {
System.out.println("不同锁begin notify: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
obj2.notify();
System.out.println("不同锁end notity: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
Thread.sleep(1000);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class WaitThread extends Thread {
private MyService myService;
public WaitThread(String name, MyService myService) {
super(name);
this.myService = myService;
}
@Override
public void run() {
myService.waitfun();
}
}
class NotifyThread extends Thread {
private MyService myService;
public NotifyThread(String name, MyService myService) {
super(name);
this.myService = myService;
}
@Override
public void run() {
myService.notifyfun();
}
}
有5个线程都在wait等待,有5个notify但是有70%概率才使用同一个对象锁obj,上面允许结果有2个wai不能正常获得锁,所以程序不会结束一直阻塞在那里。
2.6、全部通知
notifyAll方法将等待队列中所有的持有相同锁的线程唤醒进入锁池队列争抢同步锁。
package com.study;
import java.time.LocalDateTime;
/**
* wait与notify/notifyAll延迟通知
*/
public class Demo {
public static void main(String[] args) throws Exception {
MyService myService = new MyService();
for (int i = 1; i <= 5; i++) {
new WaitThread("WaitThread" + i, myService).start();
}
Thread.sleep(1000);
new NotifyThread("NotifyThread", myService).start();
}
}
class MyService {
private Object obj = new Object();
public void waitfun() {
try {
synchronized (obj) {
System.out.println("begin wait: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
obj.wait();
System.out.println("end wait: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
}
} catch (Exception e) {
e.printStackTrace();
}
}
public void notifyfun() {
try {
synchronized (obj) {
System.out.println("begin notify: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
obj.notifyAll();
System.out.println("end notity: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class WaitThread extends Thread {
private MyService myService;
public WaitThread(String name, MyService myService) {
super(name);
this.myService = myService;
}
@Override
public void run() {
myService.waitfun();
}
}
class NotifyThread extends Thread {
private MyService myService;
public NotifyThread(String name, MyService myService) {
super(name);
this.myService = myService;
}
@Override
public void run() {
myService.notifyfun();
}
}
3. 异常释放锁
学习synchronized时,有几种情况当同步方法 / 块中遇到异常会提前结束线程体。
-
sleep + interrupt:这两个方法没有先后顺序,无论哪个先执行,发生异常时线程体提前中止。
-
手动制造异常:当执行到某个程度的时候,如果想提前结束直接利用throw new Exception()。
-
使用stop方法直接摧毁线程,这个方法已经废弃了不推荐使用。
-
使用return 提前结束线程体的运行,但是这种方法并不是异常。
3.1、手动制造异常提前终止
public void notifyMethod(){
try{
synchronized (obj){
for(int i = 1;i <= 10;i++){
if(i==0){
obj.notify();
throw new Exception(); //制造异常
}
}
}
}catch (Exception e){
// e.printStackTrace();
}
}
3.2、wait + interrupt方法提前终止线程
package com.study;
import java.time.LocalDateTime;
/**
* wait与notify/notifyAll延迟通知
*/
public class Demo {
public static void main(String[] args) throws Exception {
MyService myService = new MyService();
WaitThread waitThread = new WaitThread("WaitThread", myService);
waitThread.start();
Thread.sleep(5000);
waitThread.interrupt();
}
}
class MyService {
private Object obj = new Object();
public void waitfun() {
synchronized (obj) {
System.out.println("begin wait: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end wait: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
}
}
}
class WaitThread extends Thread {
private MyService myService;
public WaitThread(String name, MyService myService) {
super(name);
this.myService = myService;
}
@Override
public void run() {
myService.waitfun();
}
}
interrupt()方法,会使wait()方法抛出InterruptedException异常。
interrupt()方法,也会使Thread.sleep()方法抛出InterruptedException异常。
public void waitfun() {
synchronized (obj) {
System.out.println("begin wait: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
try {
// obj.wait();
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end wait: " + Thread.currentThread().getName() + " " + LocalDateTime.now());
}
}
4. 总结
-
sleep睡眠是抱锁睡眠(不会释放锁),不会进入等待队列而是进入阻塞状态。如上图
-
wait()方法会立即中断(类似阻塞等待),并释放锁,进入等待队列。唤醒后仍然需要去争抢获得同步锁才能继续从中断处向下执行。
-
wait(long time)方法会在time毫秒后自动唤醒,如果提前遇到通知会提前进入等待队列。
-
notify / notifyAll 通知是随机顺序通知的,并没有固定的顺序进行通知具体的某个线程。并且只通知持有相同锁的wait线程。
-
灵活使用异常能够帮助线程体的提前终止。
参考: