日常小结-java线程状态的转移

JAVA线程的状态

Thread.State和虚拟机状态之间异同。

首先JAVA的线程状态,其定义可以从Thread.State的定义中看出

NEW:初始状态
RUNNABLE:运行状态
BLOCKED:阻塞状态
WAITING/WAIT_WAITING:等待其他线程执行相应操作后运行
TERMINATED:终止状态

但是这里还是有一个比较著名的图(图片来源),如下:
日常小结-java线程状态的转移_第1张图片

我基本上是这样理解的Thread.State中的定义可以理解为从JAVA程序看,而下面的图可以看成从虚拟机的角度看,所以有些出入。

RUNNABLE:在虚拟机中实际上应该分成两个状态,也就是就绪态和运行态,一般的操作系统的书都有这个概念。但是从程序的角度没有区分这两个状态的必要,所以也就统称可运行状态。

等待状态其实可以大概分成三大类:

BLOCKED:Thread.State中的状态指的是等待监视器锁的状态,其实也就是相当于图中的进入锁池的状态。JAVA中的内置锁和显示锁的获取过程都是表示的这样的状态。

WAITING/WAIT_WAITING:这个其实也是等待的状态,但是这个等待并不是由于锁,而是由于为满足条件。在JAVA中也就相当于是Object.wait()/Object.notify()/Object.notifyAll()以及Condition.await()/Condition.signal()/Condition.signalAll()。这个等待是由于条件为满足而挂起了线程。但是在进入等待之前会释放当前线程的锁,对应到上图中应该是进入等待队列的状态,如果条件满足了,也会尝试获得锁,这时对应的就是进入锁池的状态。注意在调用这个条件变量的等待和唤醒机制的时候必须拥有锁。

WAIT_WAITING:这里其实还有一个特殊的状态就是sleep状态。这个状态对应于图中标注的阻塞状态,在Thread.State中对应于WAIT_WAITING状态,这个状态并不会释放锁。而只是中断了当前的线程。另外还有stop()等等也是,但是这个类方法已经被遗弃了所以这里不再讨论了。

其他相关的一些小问题

sleep和Object.wait()有什么异同

这两者的不同其实根据之前的图就可看出来了:
sleep是不释放当前线程所拥有的锁的,调用的时候,进入阻塞状态(或者Thread.State中的WAIT_WAITING)。当时间到了或者中断了就会自动退出当前阻塞状态。进入就绪态(或者Thread.State中的RUNNABLE),如果被CPU选中就继续运行。
Objct.wait()是不同的,调用Object.wait()的线程必定是拥有Object的监视器锁的,在调用时进入了阻塞状态(或者Thread.State中的WAITING/WAIT_WAITING)这时候会把当前线程放入的该对象的阻塞队列中,同时会释放当前线程的所有锁。之后若有其他线程执行相关操作使得线程条件满足(Object.notify()/Object.notifyAll()),并且等待队列中的该线程被选中,线程会从阻塞队列中出来,然后重新获取锁(这里有一点不太清楚,这个获取锁的顺序如何判断?我觉得这是一个可以讨论的问题),这时候线程会进入锁池(不太清楚这时候会不会进入Thread.State的BLOCKED状态),如果获取锁成功了,则同样进入就绪态等待运行(Thread.State中的RUNNABLE)。

Object.wait()和Condition.await()方法的区别

首先其实这是两个组个,Object.wait()应该是用来配合内置锁来使用的,而Condition.await()显然是配合后来的显示锁,或者AQS构造的其他同步组件来使用的。所以这差不多是两个体系的问题,但是他们实现的功能上是差不多的,区别主要在这两种锁的灵活性上不同,当然这涉及到大量关于两种锁的讨论这里略过,具体到wait()和await()方法主要的区别就是Condition可以从一个lock中引申出多个,而Object.wait()只有一个。

当然一个条件队列也是可以用的,比如你可以唤醒全部,但是当你唤醒全部线程的时候你就需要很多不必要的上下文切换去判断额外的判断是那种情况满足条件。如果是用Condition的话就可以创建多个条件队列,这样可以减少上下文切换的次数,是更高效的策略。

中断和线程状态

这个好像没什么人讨论,可能只是我自己比较糊涂,中断其实在JAVA中等同于和线程状态关系并不大,而是以异常的形象表示,所以一个线程中断了,然后尝试去捕获这个异常,在整个过程中实际上程序还是在不断运行的,但是我们都知道JAVA的中断并不是C中的立刻响应的,而是在一些特殊的情况下才抛出这个中断的异常,所以实际上一个线程被中断的过程可以看成是从阻塞状态到就绪态的情况。当然如果你没有及时处理这个中断,会导致线程的终止。

通常中断只会在下列情况下收到itnerruptedException异常

当线程阻塞在Object.wait()方法,或者Thread.join()和Thread.sleep()方法,会收到异常
当前线程阻塞在可中断的IO操作通道上时,这个通道被关闭时,会受到异常
当线程被阻塞在Selector,然后Interrupt状态被设置,Selector会立刻返回其所选操作,可能是一个非零的值,只有当Selector的wakeup方法被调用的时候才会这样。
如果之前的条件没有被保存则中断状态会被设置,
如果没有任何处理程序则中断的线程会被终止。

条件变量、条件状态和条件队列

首先条件状态其实就是一种线程之间的协作方式,通俗的说就是线程A阻塞直到线程B执行了某种操作,使得条件状态改变到满足条件,则线程A继续运行。
条件状态的变量需要一个具体的变量去体现,在java中实际上有两种体现方式,一种是Object对象所关联的监视器,采用Object的wait()/notify()/notifyAll()来等待唤醒,另一种是通过锁所创建的condition,采用await()/signal()/signalAll()来体现。线程会通过在条件变量上的等待和唤醒进行响应协调工作。
条件队列或者说阻塞队列指的实际上是在线程在条件变量等待的时候会将当前线程放入一个队列中,这个队列叫条件队列,这个队列并不是FIFO的,如果采用notify()或者signal()唤醒条件队列中的线程会随机从队列中找到某个线程唤醒。

锁和监视器

监视器一般来说是操作系统的概念,在JAVA中是这样体现的。每个JAVA对象都关联一个监视器,这个监视器是排他的。比如你使用内置锁对该对象进行加锁,然后再synchronized 代码块中操作,也就说相当于是当前线程进行了监视器同步区。

监视器好比一做建筑,它有一个很特别的房间,房间里有一些数据,而且在同一时间只能被一个线程占据,进入这个建筑叫做”进入监视器”,进入建筑中的那个特别的房间叫做”获得监视器”,占据房间叫做”持有监视器”,离开房间叫做”释放监视器”,离开建筑叫做”退出监视器”.
来源

当然对于内置锁来说,线程进入了监视器也就是相当于程序进入了锁的同步区。但是这里有一个问题就是这个监视器是绑定给对象的而且是排他的只能绑定一个线程,但是如果你使用的是AQS构建的共享锁,按照我的理解这个概念上就不同了,共享锁的实现概念上应该更接近于信号量。具体的实现不是很了解但是应该不是使用监视器完成的,至于AQS构建的独占锁是不是监视器来实现的我猜测也不是。所以内置锁在实际上是依赖于监视器实现的,所以还有人把内置锁称为监视器锁,而其他AQS构建的锁则不是。

此外还有一个问题,就是监视器其实提供线程协同工作的方法,就是对于每个Object,java提供了wait()notify()和notifyAll()方法来实现对监视器的操控。

你可能感兴趣的:(JAVA,并发,日常小结)