当前位置 博文首页 > JavaEdge全是干货的技术号:和面试官讲完Java线程状态,当场发了o
面试官瞅了瞅我精悍的简历,随口一问,说说Java线程有哪些状态?
这问的也太基础了,这就是开场白吗?
这个简单的呀,面试官让我来告诉你,JDK 的 Thread 源码定义了6个状态: java.lang.Thread.State
Object.wait
、Thread.join
、 LockSupport.park
文字说的还不是太清楚了,让我来你画个图就一目了然了:
线程还没有开始执行
当调用线程的start()方法,线程也不一定会马上执行,因为Java线程是映射到os的线程执行的
,此时可能还需要等os调度,但此时该线程的状态已经为RUNNABLE。
只是说你有资格运行,调度程序没有挑选到你,你就永远是可运行状态。
该状态最有争议,注释说它表示线程在JVM层面是执行的,但在os不一定,它举例是CPU,毫无疑问CPU是一个os资源,但这也就意味着在等os其它资源时,线程也会是这个状态。
I/O阻塞算是等os的资源?
Object.wait
之后正在等待监视器锁 进入 同步的块/方法或 再进入 同步的块/方法被挂起,线程因为某原因放弃cpu 时间片,暂时停止运行。
Thread.sleep()
join()
,当前线程进入阻塞态等待阻塞
运行的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)
同步阻塞
运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)
其他阻塞
运行的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。
当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
线程在阻塞等待monitor lock(监视器锁)
一个线程在进入synchronized修饰的临界区的时候,或者在synchronized临界区中调用Object.wait然后被唤醒重新进入synchronized临界区都对应该态。
结合上面RUNNABLE的分析,也就是I/O阻塞不会进入BLOCKED状态,只有synchronized会导致线程进入该状态
关于BLOCKED状态,注释里只提到一种情况就是进入synchronized声明的临界区时会导致,这好理解,synchronized是JVM自己控制的,所以这个阻塞事件它自己能够知道(对比理解上面的os层面)。
interrupt()是无法唤醒的,只是做个标记。
处于等待状态的线程正在等待另一个线程执行特定操作。
例如:
wait()
后产生的一种结果由于调用以下方法之一,线程处于等待状态:
它们也是在等待另一个对象事件的发生,也就是描述了等待的意思。
BLOCKED
wait()
典型案例就是通过wait()
/notify()
完成生产者/消费者模型。
当生产者生产过快,发现仓库满了,即消费者还没有把东西拿走(空位资源还没准备好) 时,生产者就等待有空位再做事。
消费者拿走东西时会发出“有空位了”的消息,那么生产者就继续工作。
反之,当消费者消费过快发现没有存货时,消费者也会等存货到来,生产者生产出内容后发出“有存货了”的消息,消费者才继续抢东西。
这种状态下,若发生了对该线程的interrupt()
是有用的,处于该状态的线程内部会抛InerruptedException
,该异常应当在run()
里面捕获,使得run()
正常执行完成。
在run()
内部捕获异常后,还可以让线程继续运行,根据具体场景决定。
这种状态下,若某线程对该锁对象做了notify()
,则将从等待池中唤醒一个线程重新恢复到RUNNABLE
。
除notify()
外,还有一个notifyAll()
,前者是唤醒一个处于WAITING
的线程,而后者是唤醒所有的线程。
Object.wait()是否需要死等呢?
不是,除中断外,它还有两个重构方法
InterruptedException
,但是并不意味着我们不去捕获,因为不排除其他线程会对它做interrup()
)同样的
LockSupport park( )
LockSupport.parkNanos( )
LockSupport.parkUntil( )
Thread.join()
这些方法都会有类似的重构方法来设置超时,达到类似的目的,不过此时的状态不再是WAITING
,而是TIMED.WAITING
。
通常写代码的人肯定不想让程序死掉,但是又希望通过这些等待、通知的方式来实现某些平衡,这样就不得不去尝试采用“超时+重试+失败告知”等方式来达到目的。
当调用Thread.sleep()
时,相当于使用某个时间资源作为锁对象,进而达到等待的目的,当时间达到时触发线程回到工作状态。
这个线程对象也许是活的,但是,它已经不是一个单独执行的线程,在一个死去的线程上调用start()方法,会抛java.lang.IllegalThreadStateException
。
线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。
run()走完了,线程就处于这种状态。其实这只是Java 语言级别的一种状态,在操作系统内部可能已经注销了相应的线程,或者将它复用给其他需要使用线程的请求,而在Java语言级别只是通过Java 代码看到的线程状态而已。
wait( )
和notify( )
必须要使用synchronized如果不用就会报ilegalMonitorStateException
,常见写法如下:
synchronized(Object){
object.wait() ;//object.notify() ;
}
synchronized(this){
this.wait();
}
synchronized fun( ){
this.wait();//this.notify();
}
wait()
和notify()
是基于对象存在的。
不用对象也可以实现,比如suspend()/resume()就不需要,但是它们是反面教材,表面上简单,但特别容易死锁。
所以目前它调用的方式只能是Object.wait()
,这样才能和对象挂钩。但这些东西还与问题“wait()/notify() 为什么必须要使用synchronized" 没有半点关系,或者说与对象扯上关系,为什么非要用锁呢?
既然是基于对象的,就得用个数据结构存放这些等待的线程,该数据结构应当与该对象绑定(查看JVM代码,可知该数据结构为一个双向链表),此时在这个对象上可能同时有多个线程调用wait()/notify(),在向这个对象所对应的双向链表中写入、删除数据时,依然存在并发的问题,理论上也需要一个锁来控制。在JVM 内核源码中并没有发现任何自己用锁来控制写入的动作,只是通过检查当前线程是否为对象的OWNER 来判定是否要抛异常。由此可见它希望该动作由Java 程序代码自己控制,为什么JVM不选择自己控制锁呢?
因为有时更低抽象层次的锁不是好事,这样的请求对于外部可能是反复循环地去征用,或这些代码还可能在其他地方复用,也许将它粗粒度化会更好一些,而且这样的代码在写在Java 程序中也更加清晰,容易看到相互之间的关系。
interrupt()操作只对处于WAITING 和TIMED_WAITING 状态的线程有用,让它们产生实质性的异常抛出。
通常如果线程处于运行中状态,也不会让它中断,如果中断是成立的,可能会导致正常的业务运行出现问题。另外,如果不想用强制手段,就得为每条代码的运行设立检查,但是这个动作很麻烦,JVM 不愿意做这件事情,它做interrupt()仅仅是打一个标记,此时程序中通过isInterrupt()方法能够判定是否被发起过中断操作,如果被中断了,那么如何处理程序就是设计问题。
比如,若代码运行是一个死循环,则在循环中可以这样:
while(true) {
if (Thread.currentThread.isInterrupt()) {
//可做类似的break、return,抛出InterruptedException 达到某种目的,这完全由自己决定
//如拋出异常,通常包装一层try catch 异常处理,进一步做处理,如退出run或什么也不做
}
}
JDK 6 及以后,可以使用线程的interrupted( )
判定线程是否已经被调用过中断方法,表面上的效果与isInterrupted()
结果一样,不过这是个静态方法。
此外,这个方法调用后将会重新将中断状态置false
,方便循环利用线程,而不是中断后状态就始终为true,就无法将状态修改回来了。
对应的,判定线程的相关方法有isAlive()