【线程】线程的5种状态详解

1.背景

java线程有多少种状态?

答案是6种!!!

那为什么有的地方说是5种呢,那这一定是将操作系统层面的线程状态搞混了。

下面我们就分别介绍一下java线程的6种状态以及操作系统层面的5种状态。

2. 六种状态

我们先来一个官方的依据

//JDK 1.6 
public class Thread implements Runnable {
    
    
	public enum State {
    
    
		NEW,

		RUNNABLE,

		BLOCKED,

		WAITING,

		TIMED_WAITING,

		TERMINATED;
	}
}

简单来介绍一下6种状态:

  • NEW:初始状态,线程被构建,但是还没有调用 start 方法;

  • RUNNABLED:运行状态,JAVA 线程把操作系统中的就绪和运行两种状态统一称为“运行中” ;

  • BLOCKED:阻塞状态,表示线程进入等待状态,也就是线程因为某种原因放弃了 CPU 使用权,阻塞也分为几种情况 :

    • 等待阻塞:运行的线程执行了 Thread.sleepwait()join() 等方法JVM 会把当前线程设置为等待状态,当 sleep 结束join 线程终止或者线程被唤醒后,该线程从等待状态进入到阻塞状态,重新抢占锁后进行线程恢复;

      待进一步研究

    • 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其他线程锁占用了,那么jvm会把当前的线程放入到锁池中 ;

    • 其他阻塞:发出了 I/O请求时,JVM 会把当前线程设置为阻塞状态,当 I/O处理完毕则线程恢复;

  • WAITING:等待状态,没有超时时间,要被其他线程或者有其它的中断操作;

    执行wait()、join()、LockSupport.park();

  • TIME_WAITING:超时等待状态,超时以后自动返回;

    执行 Thread.sleep(long)、wait(long)、join(long)、LockSupport.park(long)、LockSupport.parkNanos(long)、LockSupport.parkUntil(long)

  • TERMINATED:终止状态,表示当前线程执行完毕 。

3. 线程的状态图

有很多的图,我把2个比较好的列出,供参考
图一:
在这里插入图片描述
把RUNNABLE细分为RUNNING和ready

图二:
在这里插入图片描述
注意:

1)sleep、join、yield时并不释放对象锁资源,在wait操作时会释放对象资源,wait在被notify/notifyAll唤醒时,重新去抢夺获取对象锁资源。

简单普及下wait(), notify()知识,二者必须在持有锁的情况下才能调用,可以理解成synchronized语句块中才行。


如何理解重新获取锁资源会引起block呢?以生产者消费者经典例子,假设有2个消费者线程A和B,此时队列为空,A获取锁后wait,释放锁,B获取锁后也会wait,二者都是waiting状态,如果生产者线程发送唤醒信号,A和B只能有一个先重新获取锁,并继续执行代码,此时另一个会由waiting变为block了

2)sleep可以在任何地方使用,而wait,notify,notifyAll只能在同步控制方法或者同步控制块中使用。

3)调用obj.wait()会立即释放锁,以便其他线程可以执行notify(),但是notify()不会立刻立刻释放sycronized(obj)中的对象锁,必须要等notify()所在线程执行完sycronized(obj)同步块中的所有代码才会释放这把锁。然后供等待的线程来抢夺对象锁。

4. 同步队列与等待队列

有的文章为了更好的描述wait引发的状态变更,引入了同步队列与等待队列概念,我在状态切换图中加入了这两个队列:
在这里插入图片描述

等待队列:指状态为wating的线程集合

  • 调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) 代码段内。

同步队列:指等待获取锁的,状态为block的线程集合

  • 当前线程想调用对象A的同步方法时,发现对象A的锁被别的线程占有,此时当前线程进入同步队列。简言之,同步队列里面放的都是想争夺对象锁的线程。
  • 当一个线程1被另外一个线程2唤醒时,1线程进入同步队列,去争夺对象锁。
  • 同步队列是在同步的环境下才有的概念,一个对象对应一个同步队列

初始状态下,等待队列中有2,3,4,他们都是调用wait()方法后进入的;同步队列中有5,6,在等待锁
在这里插入图片描述

1.线程1获取对象A的锁,正在使用对象A。
2.线程1调用对象A的wait()方法。
3.线程1释放对象A的锁,并马上进入等待队列。
4.锁池里面的对象争抢对象A的锁。
5.线程5获得对象A的锁,进入synchronized块,使用对象A。
6.线程5调用对象A的notifyAll()方法,唤醒所有线程,所有线程进入同步队列。若线程5调用对象A的notify()方法,则唤醒一个线程,不知道会唤醒谁,被唤醒的那个线程进入同步队列。
7.notifyAll()方法所在synchronized结束,线程5释放对象A的锁。
8.同步队列的线程争抢对象锁,但线程1什么时候能抢到就不知道了。

5. 几个方法的比较

  • Thread.sleep(long millis),一定是当前线程调用此方法,当前线程进入TIMED_WAITING状态,但不释放对象锁,millis后线程自动苏醒进入就绪状态。作用:给其它线程执行机会的最佳方式。

  • Thread.yield(),一定是当前线程调用此方法,当前线程放弃获取的CPU时间片,但不释放锁资源,由运行状态变为就绪状态,让OS再次选择线程。作用:让相同优先级的线程轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。Thread.yield()不会导致阻塞。该方法与sleep()类似,只是不能由用户指定暂停多长时间。

  • t.join()/t.join(long millis),当前线程里调用其它线程t的join方法,当前线程进入WAITING/TIMED_WAITING状态,当前线程不会释放已经持有的对象锁。线程t执行完毕或者millis时间到,当前线程进入就绪状态。

  • obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout) timeout时间到自动唤醒。

  • obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。

参考 线程的5种状态详解 有点出入

一文搞懂线程世界级难题——线程状态到底是6种还是5种!!!

Java线程的6种状态及切换(透彻讲解) 图一来源

java多线程的几种状态 图二来源

猜你喜欢

转载自blog.csdn.net/m0_45406092/article/details/108701347