Java并发编程(3) —— 线程的生命周期和状态
迪丽瓦拉
2024-06-02 19:32:00
0

一、Java线程的六种状态

Thread类中的枚举类State展示了Java线程的六种状态,线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态:

  • NEW: 初始状态,线程被创建出来但没有被调用 start() 。
  • RUNNABLE: 运行状态,线程被调用了 start()开始运行的状态。
  • WAITING:等待状态,表示该线程需要等待其他线程做出一些特定动作(通知或中断)才能恢复运行。
  • TIME_WAITING:超时等待状态,可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。
  • BLOCKED :阻塞状态,需要等待锁释放。
  • TERMINATED:终止状态,表示该线程已经运行完毕。
    在这里插入图片描述

二、改变线程状态的方法

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():当前线程释放对object对象的锁,并随机从该对象的等待池中唤醒一个线程从等待状态进入运行状态,被唤醒的的线程会进入该对象的锁池中。
object.notifyAll():当前线程释放对object对象的锁,并唤醒该对象的等待池中的所有线程进入该对象的锁池中。

wait()方法和notify()方法都只能在synchonized代码中调用,否则运行时会报非法监视器状态异常(llegalMonitorStateException),道理也很简单,首先你得持有锁才能释放锁。
参考:

  1. wait会释放锁?当然会释放锁,代码见真招
  2. java中的notify和notifyAll有什么区别?

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对象等待池中的线程,这就包括了当前线程,使当前线程回到当前线程继续执行下去。
thread.join(mills):同上,只不过是超时等待,超时结束后进入thead对象锁池。

join()方法的底层原理:

  • join()方法的底层是利用wait()方法实现。
  • join()方法是一个同步方法,当主线程调用t1.join()方法时,主线程先获得了t1对象的锁。
  • join()方法中调用了t1对象的wait()方法,使主线程进入了t1对象的等待池。

当thread对象的run方法执行结束后,会notifyAll唤醒所有thead对象等待池中的线程。
参考:Java 多线程join()方法的作用和实现原理解析

三、其他方法

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异常后,主动退出)

参考:对Thread.interrupt()的理解

四、案例:用wait()和notify()实现生产者-消费者模型

以下为一个简单的生产者-消费者模型案例

生产者:多个生产者线程并发生产消息放入队列中,每生产一个消息都唤醒所有消费者线程,队列有最大长度,当队列满则生产者线程进入等待状态。
消费者:多个消费者线程从队列中取消息,当队列为空则消费者线程进入等待状态,并唤醒所有生产者线程进行生产。

//生产者-消费者队列
public class WaitNotifyQueue {private final LinkedList 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 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();}
}

参考:

  1. https://www.zhihu.com/question/283851222/answer/1886090367
  2. https://www.bilibili.com/video/BV1aL4y1e7aj

相关内容