一、线程的六种状态
1.1、New 新创建的
new表示线程被创建但尚未启动的状态
Thread thread=new Thread();
1.2、Runnable 可运行的
当线程调用start方法,线程就处于Runnable状态。
thread.start();
Java中Runnable状态对应操作系统线程状态:Running和Ready
Java中处于Runnable状态的线程有可能正在执行,也有可能没有正在执行,正在等待被分配CPU资源
1.3、阻塞状态
阻塞状态又包含三种状态:
blocked与waiting的区别:blocked是等待其他线程释放monitor锁,而waiting是等待某个条件
-
Blocked 被阻塞的
Runnable状态进入blocked状态只有一种可能,就是进入synchronized保护的代码时没有抢到monitor锁。(无论是进入synchonized代码块,还是synchronized方法,都是一样的)
当处于
blocked的线程抢到了monitor锁,就会从Blocked状态回到Runnable状态 -
Waiting 等待
有三种情况,会进入waiting状态:
- 没有设置timeout参数的Object.wait()方法
- 没有设置timeout参数的Thread.join()方法
- LockSupport.park()方法
从waiting重新进入runnable状态
- LockSupport.unpark()
- join的线程运行结束或者被中断
从waiting进入其他状态
- o.notifyAll()
- o.notify()
因为唤醒waiting线程的线程如果调用notify或者notifyAll()。要求必须首先持有该monitor锁。所以处于waiting状态的线程被唤醒时拿不到该锁,就会进入block状态。拿到了锁了就进入runnable状态
-
Timed_waiting 计时等待
Timed Waiting与Waiting是相似的,Timed Waiting会等待超时。系统会自动唤醒,或者在超时时间前被唤醒信号唤醒
- 设置了时间参数的Thread.sleep(long millis)
- 设置了时间参数的Object.wait(long timeout)
- 设置了时间参数的Thread.join(long millis)
- 设置了时间参数的LockSupport.parkNanos(long nanos)、LockSupport.partUtil(long deadline)
从Timed_waiting重新进入runnable状态
- LockSupport.unpark()
从Timed_waiting重新进入其他状态
- o.notifyAll()
- o.notify()
因为唤醒Timed_waiting线程的线程如果调用notify或者notifyAll()。要求必须首先持有该monitor锁。所以处于waiting状态的线程被唤醒时拿不到该锁,就会进入block状态。拿到了锁了就进入runnable状态
1.4、Terminated 被终止的
- run方法执行完毕,线程正常退出
- 出现一个没有捕获的异常,终止了run方法,最终导致以外终止
二、线程转换的注意点
- 线程的状态是需要按照箭头方向来走的,比如线程从new状态是不可以直接进入blocked状态的,它需要先经历Runnable状态
- 线程生命周期不可逆:一旦进入runnable状态就不能会到new状态。一旦被终止就不可能再有任何状态变化。所以一个线程只能一次
New和Terminated状态,只有处于中间才可以相互转换
三、Java线程的状态的流程图扭转
四、wait、notify、notifyAll方法的使用注意事项
4.1、为什么wait必须在synchronized保护的同步代码中使用?
/**
* Thrown to indicate that a thread has attempted to wait on an
* object's monitor or to notify other threads waiting on an object's
* monitor without owning the specified monitor.
*
* @author unascribed
* @see java.lang.Object#notify()
* @see java.lang.Object#notifyAll()
* @see java.lang.Object#wait()
* @see java.lang.Object#wait(long)
* @see java.lang.Object#wait(long, int)
* @since JDK1.0
*/
public
class IllegalMonitorStateException extends RuntimeException {
其实意思就是说,也就是当前的线程不是此对象监视器的所有者。也就是要在当前线程锁定对象,才能用锁定的对象此行这些方法,需要用到synchronized ,锁定什么对象就用什么对象来执行 notify(), notifyAll(),wait(), wait(long), wait(long, int)操作,否则就会报IllegalMonitorStateException异常。
也就是在说,就是需要在调用wait()或者notify()之前,必须使用synchronized语义绑定住被wait/notify的对象。
所以运行下面这段代码是报错的
package com.strivelearn.concurrent.chapter02;
import java.util.LinkedList;
import java.util.Queue;
import java.util.UUID;
/**
* @author strivelearn
* @version BlockingQueue.java, 2022年12月24日
*/
public class BlockingQueue {
Queue<String> buffer = new LinkedList<>();
/**
* 负责往buffer中添加数据
* 添加完之后执行notify方法
*/
public void give() {
buffer.add(UUID.randomUUID().toString());
notify();
}
/**
* 负责检查整个buffer是否为空
* 如果为空就进入等待,如果不为空就remove队列第一个数据
* @return
*/
public String take() {
while (buffer.isEmpty()) {
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
return buffer.remove();
}
public static void main(String[] args) {
BlockingQueue queue = new BlockingQueue();
Thread producer = new Thread(queue::give);
Thread consumer = new Thread(queue::take);
consumer.start();
producer.start();
}
}
正确的写法:
package com.strivelearn.concurrent.chapter02;
import java.util.LinkedList;
import java.util.Queue;
import java.util.UUID;
/**
* @author strivelearn
* @version BlockingQueue.java, 2022年12月24日
*/
public class BlockingQueueRight {
Queue<String> buffer = new LinkedList<>();
/**
* 负责往buffer中添加数据
* 添加完之后执行notify方法
*/
public void give() {
synchronized (this) {
buffer.add(UUID.randomUUID().toString());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
notify();
}
}
/**
* 负责检查整个buffer是否为空
* 如果为空就进入等待,如果不为空就remove队列第一个数据
* @return
*/
public String take() {
synchronized (this) {
while (buffer.isEmpty()) {
try {
System.out.println("等待中...");
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("等待结束");
return buffer.remove();
}
}
public static void main(String[] args) {
BlockingQueueRight queue = new BlockingQueueRight();
Thread producer = new Thread(queue::give);
Thread consumer = new Thread(queue::take);
consumer.start();
producer.start();
}
}
- 这样可以确保notify方法永远不会在buffer.isEmpty()、wait()之间被调用,提升程序的安全性
- 另外,wait方法会释放monitor锁,这也要求我们必须首先进入synchorized内持有这把锁
4.2、为什么wait、notify、notifyAll被定义在Object类中而sleep定义在Thread类中
因为Java中每个对象都有一把称为monitor监视器的锁,由于每个对象可以都上锁,这就要求在对象头中有一个用来保存锁信息的位置。这个锁是对象级别的,而非线程级别的,wait、notify、notifyAll 也都是锁级别的操作,它们的锁属于对象。所有把它们定义在Object类中最合适,因为Object类是所有对象的父类
如果把wait、notify、notifyAll方法定义在Thread类中,会带来很大的局限性。比如一个线程可能持有多把锁,以便实现相互配合的复杂逻辑,假设此时wait方法定义在Thread类中,如何实现让一个线程持有多把锁?如何明确线程等待的是哪把锁?
我们让当前线程去等待某个对象的锁,自然应该通过操作对象来实现,而不是操作线程
4.3、wait、notify和sleep方法的异同
相同点:
- 它们都可以让线程阻塞
- 它们都可以响应interrupt中断,在等待的过程中如果收到中断信号,都可以进行响应,并抛出InterruptedException异常
不同点:
- wait方法必须在synchronized保护的代码中使用,而sleep方法并没有这个要求
- 在同步代码中执行sleep方法时,并不会释放monitor锁,但是执行wait方法时会主动释放monitor锁
- sleep方法中要求必须定义一个时间,时间到期后会主动恢复,而对于没有参数的wait方法而言,意味着永久等待,直到被中断或者被唤醒才能恢复,它并不会主动恢复
- wait/notify是object类的方法,而sleep是Thread类的方法
五、如何使用wait、notify、condition、blockingQueue实现生产者消费者模式
生产者消费者模式是程序设计中非常常见的一种设计模式,被广泛运用在解耦、消息队列等场景
什么时候阻塞线程需要被唤醒呢?
- 当消费者看到阻塞队列为空时,开始进入等待,这时生产者一旦往队列中放入数据,就会通知所有的消费者,唤醒阻塞的消费者线程
- 如果生产者发现队列已经满了,也会被阻塞,而一旦消费者获取数据之后就相当于队列空了一个位置,这时消费者就会通知所有正在阻塞的生产者进行生产
package com.strivelearn.concurrent.chapter02;
import java.util.LinkedList;
import java.util.UUID;
/**
* @author strivelearn
* @version MyBlockingQueue.java, 2022年12月24日
*/
public class MyBlockingQueue {
private int maxSize;
private LinkedList<String> storage;
public MyBlockingQueue(int size) {
this.maxSize = size;
this.storage = new LinkedList<>();
}
public synchronized void put() throws InterruptedException {
// 当前仓储容量达到上限的时候,阻塞线程
while (storage.size() == maxSize) {
wait();
}
// 当未达到仓储容量上限时,生产数据
storage.add(UUID.randomUUID().toString());
// 唤醒阻塞的消费者线程(认为仓储容器为0的线程)开始消费
notifyAll();
}
public synchronized void take() throws InterruptedException {
// 当仓储容量为0时,阻塞消费者线程,等待生产者线程进行消费
while (storage.size() == 0) {
wait();
}
System.out.println("消费:" + storage.remove());
// 唤醒阻塞的生产者线程(认为仓储容量满的线程)开始生产
notifyAll();
}
}