一、Java线程的六种状态
Thread类中的枚举类State展示了Java线程的六种状态,线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态:
- NEW: 初始状态,线程被创建出来但没有被调用 start() 。
- RUNNABLE: 运行状态,线程被调用了 start()开始运行的状态。
- WAITING:等待状态,表示该线程需要等待其他线程做出一些特定动作(通知或中断)才能恢复运行。
- TIME_WAITING:超时等待状态,可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。
- BLOCKED :阻塞状态,需要等待锁释放。
- TERMINATED:终止状态,表示该线程已经运行完毕。
Linux中线程(即轻量级进程)的五种状态
二、改变线程状态的方法
1. thread.start()
thread.start():线程对象thread刚创建时状态为NEW,在调用start()方法后进入RUNNABLE状态开始运行,等待系统调度获得CPU时间片,线程这时候处于 READY(可运行) 状态。可运行状态的线程获得了 CPU 时间片(timeslice)后就处于 RUNNING(运行) 状态。
在操作系统层面,线程有 READY 和 RUNNING 状态;而在 JVM 层面,只能看到 RUNNABLE 状态
2. object.wait() object.wait(mills)
object.wait():当前线程释放对object对象的锁,并使当前线程从RUNNABLE状态进入WAITING状态,进入到该对象的等待池中,等待池中的线程不会去竞争对象锁。
object.wait(mills):当前线程释放对object对象的锁,使当前线程从RUNNABLE状态进入TIME_WAITING状态,进入到该对象的等待池中,超时等待。当超时时间结束后,并进入该对象的锁池中,锁池中的线程会去竞争该对象锁。
当线程结束等待状态进入锁池竞争锁时,若竞争失败则会进入BLOCKED状态,只有在获得锁后才会重新回到到RUNNABLE状态继续执行下去。
3. object.notify() object.notifyAll()
object.notify():随机从该对象的等待池中唤醒一个线程从等待状态进入运行状态(依赖于具体jvm实现,Hotspot中是顺序唤醒),被唤醒的的线程会进入该对象的锁池中。
object.notifyAll():唤醒该对象的等待池中的所有线程进入该对象的锁池中。
wait()方法和notify()方法都是Object类中的方法,都只能在synchonized代码中调用,否则运行时会报非法监视器状态异常(llegalMonitorStateException)。调用wait()方法后会释放锁,而notify()不会释放锁
参考:
4. Thread.sleep(mills)
Thread.sleep(mills):静态方法,暂停当前线程(不会释放锁),使线程从RUNNABLE状态进入TIME_WAITING状态,超时等待。当超时时间结束后,线程将会返回到 RUNNABLE 状态。
5. thead.join() thead.join(mills)
thead.join():使当前线程获取thead对象的锁,然后调用thead.wait()使当前线程进入等待状态,进入thead对象的等待池,当thead线程执行完毕时会notifyAll唤醒所有thead对象等待池中的线程进入锁池,这就包括了当前线程,当当前线程获得锁后再继续执行下去。也就是说join()方法大概相当于将子线程与主线程合并按顺序执行,当执行完子线程后,接着继续执行主线程。
thread.join(mills):同上,只不过是超时等待,超时结束后进入thead对象锁池。
join()方法的底层原理:
- join()方法的底层是利用wait()方法实现。
- join()方法是一个同步方法,当主线程调用t1.join()方法时,主线程先获得了t1对象的锁。
- join()方法中调用了t1对象的wait()方法,使主线程进入了t1对象的等待池。
- 当thread对象的run方法执行结束后,会notifyAll唤醒所有thead对象等待池中的线程。
三、其他方法
6. thread.yield()
thread.yield():使处于RUNNABLE状态的thread线程回到就绪状态,重新等待CPU调度运行。
7. thread.interrupt()
thread.interrupt():当前线程给thread线程设置一个中断标志位。关于这个方法的理解如下:
- .一个线程不应该被其他线程中断
- interrupt()的作用不是中断某个线程,而是通知一个线程应该中断了。
- 被通知中断的线程具体是中断还是选择继续运行由该线程自己决定。
- 具体来说,当对一个线程调用interrupt()方法时
1)当线程处于sleep()、wait()、join()等状态时,该线程将立即退出阻塞状态,然后抛出一个InterruptedException异常
2)当线程处于正常状态时,该线程的中断标志位将被置为true,仅此而已。线程将继续正常运行。 - interrupt()方法并不会中断线程,需要线程本身配合才行。 也就是说,一个线程如果有中断的需求,可以这样做:
1)在运行时,经常检查自己的中断标志位(Thread.isInterrupted():判断是否被中断。Thread.interrupted():判断是否被中断,并清除标志位。),然后做出想要的操作。
2)在调用阻塞方法[sleep()、wait()、join()]时,需要正确处理InterruptedException异常(如catch异常后,主动退出)
8. thread.stop()
强制终止线程运行,过时方法 不推荐使用。若有中断线程需求可使用如下两种方法
- 设置一个线程共享变量作为标志位,线程运行过程中检查标志位。
- 使用thread.interrupt(),线程运行过程中检查是否被中断。
四、案例:用wait()和notify()实现生产者-消费者模型
以下为一个简单的生产者-消费者模型案例
生产者:多个生产者线程并发生产消息放入队列中,每生产一个消息都唤醒所有消费者线程,队列有最大长度,当队列满则生产者线程进入等待状态。
消费者:多个消费者线程从队列中取消息,当队列为空则消费者线程进入等待状态,并唤醒所有生产者线程进行生产。
//生产者-消费者队列
public class WaitNotifyQueue<T> {
private final LinkedList<T> queue = new LinkedList<>();
private final static int MAX_COUNT = 10;//队列最大长度
public synchronized void put(T resource) throws InterruptedException {
while (queue.size() >= MAX_COUNT) {
//队列满了,不能再塞东西了,轮询等待消赉者取出数据
System.out.println("生产者:队列已满,无法插入...");
this.wait();//被唤醒后队列可能已经被前面唤醒的生产者重新填满了,故需循环判断
}
System.out.println("生产者:插入"+resource + "! ! !");
queue.addFirst(resource);
this.notifyAll();
}
public synchronized void take() throws InterruptedException {
while (queue.size() <= 0) {
//队列空了,不能再取东西,轮询等待生产者插入数据
System.out.println("消费者:队列为空,无法取出...");
this.wait();//被唤醒后队列仍可能为空,故需循环判断
}
System.out.println("消费者:取出消息! !!");
queue.removeLast();
this.notifyAll();
}
}
class Test {
public static void main(String[] args) {
WaitNotifyQueue<String> waitNotifyQueue = new WaitNotifyQueue<>();
//生产者线程 可多个
new Thread(() -> {
for (int i = 0;i<100;i++) {
try {
waitNotifyQueue.put("消息" + Thread.currentThread().getName() + i);//生产
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
//消费者线程 可多个
new Thread(() -> {
for (int i = 0;i<100;i++) {
try {
waitNotifyQueue.take();//消费
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
参考:
- https://www.zhihu.com/question/283851222/answer/1886090367
- https://www.bilibili.com/video/BV1aL4y1e7aj