操作系统层面与JAVA层面的线程状态
操作系统层面的五种线程状态
学过操作系统进程管理这一章的都知道,在计算机底层线程是有5中状态的:初始态、就绪态、运行态、阻塞态、结束态。
这五种状态是这么变化的:
当用户点击exe文件起到加载到内存中这段时间就是初始态,等待CPU时间片的时间里就是就绪态,得到CPU的时间段就是运行态,当在运行时遇到了需要IO的时候,就会进入阻塞态,因为要去进行IO操作,等IO完了,要先进入就绪状态等待CPU的调度,等运行完了就进入结束态了,也就是死亡了。
这就是操作系统层面的线程状态变化,而在JAVA层面有些许不同。
JAVA层面的六种线程状态
在Java层面,线程是有六种状态的,分别是:新建态、可运行态、阻塞态、等待态、计时等待态、终止态。
它的转换图是这样的:
从图中我们可以看出,RUNNABLE状态也就是可运行状态是包含了操作系统层面的就绪态和运行态以及阻塞态的,也就是说当java创建的线程在计算机底层进行调度或者IO阻塞的时候在java里一概都是可运行的状态,那什么时候是其他五种状态呢?新建态就不用说了,当我们new一个线程出来但是没有运行start()
方法的时候,这个线程就是处于新建状态,而终止态也不用说了嘛,运行结束了也就终止了。我们着重说说其他三种状态的转换过程。
RUNNABLE和BLOCKED转换
当线程1和线程2争夺锁的时候,线程1抢先拿到了锁,线程2没拿到,就要在monitor的EntryList里等着,这个时候线程2就是BLOCKED的状态了,是不是很简单!这里我们要怎么去理解这个锁呢,如果你学习了synchronized底层原理的话你应该是知道的,而如果你没有学习的话,那我给你简单说说。
synchronized经历了锁升级一系列过程后(锁升级过程参考博客synchronized锁升级),升级到了重量级锁,在字节码层面也就是经历了moniterenter这个指令,monitor锁就相当于一个房子里有三间房间,分别是_owner、_EntryList、_WaitSet,夺得锁的线程就是这把锁的_owner,夺取失败的就在_EntryList里带着,在_EntryList里的线程就等于被阻塞了,因为得在这个房间里管着等待owner线程运行完释放这把锁了,阻塞的线程才可能变成这把锁的主人,也就是从BLOCKED转换成了RUNNABLE了。
RUNNABLE和WAITING转换
以上述锁为基础,我们来看RUNNABLE和WAITING的转换是怎样的。
相信我们都知道wait()
方法和notify()
方法是啥东西,什么?你该不会连这个都不知道吧?那我再给你简单讲讲好了害。wait()
方法不能够单独使用(单独使用会报错,因为莫得锁),它必须和synchronized一起使用,当运行了wait()
方法的时候会直接升级到重量级锁,所以说白了wait()
方法必须和monitor监视锁一起使用。上面说monitor锁的时候,我们不是说到了有三个房间嘛,我们后面讲了_EntryList和_owner这两个房间,还有个_WaitSet没有讲对不对,这里就讲到啦。当我们在owner线程(也就是拿到了这把锁的线程)里调用wait()
方法的时候,这个owner线程就会进入WAITING状态,也就是进入到_WaitSet这个房间里开始自闭,也就是啥也不干就干等着啦,如果我们不去唤醒它的话。这个房间里会存放0个或多个线程,所以当我们调用notify()
方法时,会随机从_WaitSet这个房间里拿出一个线程将其唤醒。那要是想一次性全部唤醒行不行呢?当然是可以的,我们还有一个notifyAll()
方法呢!唤醒之后我们的线程也就从WAITING状态转换成RUNNABLE状态啦。
当然这只是线程进入WAITING状态的一种情况了,像线程的sleep()
方法LockSupport
的park()
方法其实也可以的。
RUNNABLE和TIMED_WAITING转换
其实这个转换过程就很简单啦,顾名思义嘛,定时的等待,那我们很容易就想到上面说到的wait()
、sleep()
和park()
都还有个有参数的重载方法的嘛,这个参数就是一个long类型的数字,也就是告诉这个线程,你听着,我就让你等这么长时间,时间到了你就自己醒过来知道不。比如sleep(2000)
,就是让线程等两秒,两秒之后就恢复可运行状态。
这就是我对操作系统层面与JAVA层面的线程状态的总结啦。
如有错误,欢迎指正!