Java线程的几种状态 以及synchronized和Lock造成的线程状态差异,一篇让你搞明白
关于作者:
一个深耕自己,不内耗的长期主义者。一个对技术充满激情,对工作对生活充满热情的热血青年。坚持用通俗易懂的大白话写技术博文,并会持续更新。
和之前一样,今天还是用通俗易懂的大白话来写点我自己的理解和总结,今天讲一下线程的几种状态之间的转换以及synchronized和Lock造成的线程状态差异。看完如果有什么疑问的地方,可以留言讨论,也可以私信。我坚信,真正能让大家看懂的技术文章才是好文章,这也是我最初决定写文章最主要的目标和动力,希望这篇文章对你有所帮助。
一、线程的几种状态
大白话描述一下线程几种状态:
-
1、New 新建状态:此时线程刚被new出来,还没执行start()方法。
-
2、Runnable 状态:具体又分为Ready就绪和Running运行。Ready就绪是线程已经准备好了,但还没有得到 CPU 时间片就等CPU时间片了,获得时间片,CPU调度后就会从Ready变成Running运行。
-
3、Blocked 阻塞状态:也称临时状态。这种状态是线程竞争资源失败而被挂起阻塞的,进入Blocked状态后,是在等待一个不可避免的竞争资源(比如锁),它还是有机会去获得锁资源,获取CPU执行权的。
-
4、Waiting 等待状态,是调用 锁监视对象.wait()方法导致的状态,线程自己不会醒,需要用notify或notifyAll来唤醒。在唤醒之前直接放弃竞争cpu资源的资格,cpu轮转切换执行权的时候连考虑都不会考虑它的。
-
5、Timed_waiting 限时等待状态,是调用object.wait(1000)、Thread.sleep(1000)这种方法传入一个时间,时间到了以后线程自己会醒。它也是在唤醒之前直接放弃竞争cpu资源的资格,cpu轮转切换执行权的时候连考虑都不会考虑它的。
-
6、Terminal 终止消亡状态:表示该线程已经执行完毕。
注意:有的书或文章又将 waiting和time_waiting统称为冻结状态。
了解过线程的几种状态后,下面再来说一下几种状态之间的转换
二、线程几种状态之间的转换
三、关键点总结
根据上图,总结一些关键的点:
-
RUNNABLE 包含就绪(Ready)和运行中(Running),Java是把这2个整体称为RUNNABLE状态。
-
synchronized拿不到锁,导致的线程状态是blocked状态。而ReentrantLock这种lock,拿不到锁而导致的线程状态是waiting状态,不是blocked。
-
blocked是当持有锁的线程释放了锁时,JVM会从锁对象监视器的同步队列里唤醒一个blocked状态的线程。注意,这里的唤醒,并不是直接就让这个线程从blocked变成RUNNABLE,而只是唤醒它,让它可以去竞争锁资源。如果竞争锁成功,它的状态变为RUNNABLE,如果竞争失败它的状态不变,继续是blocked。但是,总的来说blocked期间,还是有机会拿到CPU执行权的。
-
而waiting是只要没有notify、notifyAll或LockSupport.unpark() 去主动唤醒,那它就一直是waiting,在这期间CPU调度的时候会直接忽略这些waiting的线程,它们是不可能拿到CPU执行权的。
再说一下线程进入blocked和waiting的几种途径:
线程进入blocked的途径:进入blocked的途径只有一个,那就是synchronized导致的抢不到锁的这些线程会被放入锁对象监视器里维护的同步队列里,并且线程状态会变成blocked。
线程进入waiting状态的途径:
1、进入waiting的第一个途径就是:线程抢到了synchronized的锁,进入同步代码块执行的时候,又调用了object.wait() 方法,主动放弃CPU的执行权,这种情况线程状态会变成waiting,并且会被放入另外一个等待队列中。
注意不要搞混了,其实synchronized这种加锁的方式,JVM底层是维护了两个队列。一个是前边说的同步队列,它是用来存那些抢synchronized锁失败而导致blocked的线程的。另一个就是现在说的等待队列,它是用来存那些抢synchronized锁成功了,但是又调用了object.wait() 方法,主动放弃CPU的执行权而导致waiting的线程。这两个队列都是JVM内部底层用c++写的,而不是Java里的Queue,BlockingQueue这些队列。
2、进入waiting的第二个途径:没抢到ReentrantLock这种锁而导致的waiting,因为ReentrantLock它最后是通过调用lockSupport.park()来做的,调用这个方法就会使线程进入waiting状态。
3、进入waiting的第三个途径:抢到ReentrantLock这种锁了,但是又主动调用了condition.await()而导致的waiting,这种情况最后也是调用lockSupport.park()来做的。
注意:ReenTrantLock这种,也是维护了两个队列来存储抢锁失败而阻塞的线程 和 获取锁成功后主动放弃执行权阻塞的线程。一个底层是用内部Node类组成的双链表结构,另一个是用内部ConditionObject和ConditionNode组成的单链表结构,它们是java写的,有时间了可以看下里边具体的源码。
四、理清楚线程最用的几个方法
最后再来说一下线程中常用的几个方法,wait()、notify()、notifyAll()、sleep()、yield()、join() 这几个方法,估计很多人搞不清楚是什么情况去调用,也弄不清楚到底是Thread的方法还是锁对象的方法,或者是线程对象的方法,下面来总结说一下,一次搞清楚。
1、wait()、notify()、notifyAll()
obj.wait()
obj.notify()
obj.notifyAll()
这3个方法都是synchronize锁对象上的方法,上边例子中obj是synchronize同步代码块的那个锁对象。
obj.wait()导致线程进入waiting状态
obj.notify() 是随机唤醒一个该锁监视器上挂起的线程
obj.notifyAll() 是唤醒所有挂在该锁监视器上的线程
注意:唤醒之后是进入RUNNABLE的Ready状态,而不是直接进入Running去执行,可能还需要等待CPU时间片。
2、sleep()、yield()
sleep()、yield()这2个方法是线程类Thread类里的静态方法
Thread.sleep(1000) 在哪个线程里执行的,就睡眠哪个线程
Thread.yield() 在哪个线程里执行的,就让出哪个线程的执行权,但不保证效果
3、join()
join()方法是 thread线程对象调用的方法
比如,thread6.join() 阻塞线程,是这句话在哪个线程里执行,就阻塞哪个线程。比如在主线程里执行thread6.join();那主线程就会暂时阻塞,让thread6这个线程先执行,直到thread6执行完成,主线程才会继续执行。从join这个方法名也能大概看出来,就是让thread6这个线程先加入执行,让执行这句话的那个线程等一等。
ok,今天就写到这里吧。我坚信,真正能让大家看懂的技术文章才是好文章。这也是我最初决定写文章最主要的目标和动力。希望这篇文章对你有所帮助,欢迎留言讨论,后续会更新更多用大白话来讲的内容。
下一篇继续深入的讲一下:Java中Synchronized的逻辑
以上写的都是自己的理解和总结,纯手敲,原创不易,如果觉得文章对你有帮助,可以关注一下,感谢。