JAVA线程间的状态转换

了解一个东西最好的方式,就是去看源码,只要源码不会骗你。

解惑:
下面罗列的这些文章,看一遍,反正每篇文章的出发点都不同,多少都有点问题,有的说的也互相冲突。

  • 一题带你彻底理解 sleep() 和 wait()
  • 多线程小抄集(断)
  • 深入JVM锁机制1-synchronized
  • 深入JVM锁机制2-Lock
  • 一张图让你看懂JAVA线程间的状态转换
  • 并发编程锁之ReentrantLock总结
  • 并发编程锁之synchronized总结
  • 死磕Synchronized底层实现--重量级锁
  • Java的wait()、notify()学习三部曲之一:JVM源码分析
  • JVM源码分析之Object.wait/notify实现

下面都是我原来不成熟的想法,没删是提醒自己要懂得感恩。

昨天看了一篇朋友圈的分享,解释sleep()和wait(),题目做对了,但是文章的解释罗列了一堆网上的copy,看的我云里雾里,写文章的人啊,要对自己写的负责啊,哪怕是语法上也一定要准确啊。

一题带你彻底理解 sleep() 和 wait()
反正我读的时候有2个地方糊涂了。
比如:
优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中.

我看了2遍才理解,你好好看说的确实没问题,就是两个它放在一句上了,一下子就把人整串了。

今天参考别的文章又看了一遍,这话懂的人确实读了没毛病,如果给不懂的人看的话,确实像我一样,先看的时候会整串了,我觉得修改成下面这样会好一些:

优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中;获得执行权限的线程在执行过程中调用wait()方法,它才会重新回到等待池中.

还有:
(notify 后,当前线程不会马上释放该对象锁,wait 所在的线程并不能马上获取该对象锁,要等到程序退出 synchronized 代码块后,当前线程才会释放锁,wait所在的线程也才可以获取该对象锁)
wait 所在的线程并不能马上获取该对象锁,wait所在的线程也才可以获取该对象锁==》线程都wait了,不是先进锁池吗,大家统一去竞争吗?

12月5号我是这么理解的:
其实人家说的也没毛病,哈哈,我错了,线程被唤醒后,应该是进入竞争资源的状态,竞争失败才会进锁池。被唤醒的线程应该跟锁池上的线程一起去竞争资源,竞争失败都进锁池里面。

反正我个人觉得,写文章的时候至少要认真负责,最好把复杂的概念用图表 用流程图等辅助手段说明白了,不用真的去罗列文字,因为这东西毕竟不是笔记,给自己看的,你说你辛苦写半天,完了把人误导了,你亏不亏。
我膨胀了,保留要删除的文字是提醒自己学会感恩。

正确的文章姿势:
一张图让你看懂JAVA线程间的状态转换

线程间的状态转换:

  1. 新建(new):新创建了一个线程对象。

  2. 可运行(runnable):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。

  3. 运行(running):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。

  4. 阻塞(block):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:

(一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。

(二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。

(三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。

  1. 死亡(dead):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。

其实上面那几篇文章如果你看过的话,最重要的就是这几个图。

sync线程状态图

JAVA线程间的状态转换_第1张图片
image.png

另一个线程状态图:

JAVA线程间的状态转换_第2张图片
image.png

sync的线程转换图

JAVA线程间的状态转换_第3张图片
image.png

sync锁深度解析图

JAVA线程间的状态转换_第4张图片
image.png

待明确的问题:
1 等待队列、锁池的数据结构到底张什么样子?

  • 锁池: contentionList+entryList。
  1. contentionList是一个lock-free的数据结构,基本上就是node节点,节点的指针指向下一个节点,有头尾两个节点,加入队列的线程通过cas修改头节点,出队的通过owner线程(拥有执行权限的线程)从尾节点取出,扔到entryList队列里面去。
  2. entryList:owner线程(拥有执行权限的线程)会操作这个对列,干两件事,一个是将一批或者一个等待的节点(一个还是一批存疑啊)从contentionList挪过来,另一个是将entryList的头节点指向OnDeck,OnDeck获得抢锁的权限,与人品好刚进来就能抢锁的线程一起争抢锁。entryList是FIFO的队列。
  3. 锁池设置成这种结构就是为了减少线程并发对队列的争用,所有未抢到锁准备进入锁池的线程通过cas方式去循环更新contentionList的头节点,只有owner线程偶尔去操作下contentionList,主要操作entryList,这样就减少了队列的争用。
  • 等待队列:WaitSet。
    数据结构应该就是个Set(存疑)吧,owner线程执行notify()方法,随机选择一个线程扔到entryList;执行notifyaAll()方法唤醒所有线程进入entryList;
    其实是根据底层策略的。

2 线程被notify唤醒后,到底会不会先进锁池呢?
线程被notify唤醒后,会被owner从WaitSet移动到cxq或EntryList中去
其实是根据底层策略的,默认策略是2 如下:

如果EntryList是空队列,则iterator单个节点构成一个双向环形链表,然后_EntryList指向该节点;==》就是进入EntryList
如果EntryList不为空,通过CAS将iterator入队cxq,并将_cxq指针指向iterator; ==》就是进入cxq

贴一下别人总结的:
根据QMode的不同,有不同的处理方式:

  1. QMode = 2,并且_cxq非空:取_cxq队列排头位置的ObjectWaiter对象,调用ExitEpilog方法,该方法会唤醒ObjectWaiter对象的线程,此处会立即返回,后面的代码不会执行了;
  2. QMode = 3,并且_cxq非空:把_cxq队列首元素放入_EntryList的尾部;
  3. QMode = 4,并且_cxq非空:把_cxq队列首元素放入_EntryList的头部;
  4. QMode = 0,不做什么,继续往下看;
    只有QMode=2的时候会提前返回,等于0、3、4的时候都会继续往下执行:
    如果_EntryList的首元素非空,就取出来调用ExitEpilog方法,该方法会唤醒ObjectWaiter对象的线程,然后立即返回;
    如果_EntryList的首元素为空,就取_cxq的首元素,放入_EntryList,然后再从_EntryList中取出来执行ExitEpilog方法,然后立即返回;

3 锁池上的线程到底是怎么被唤醒去竞争资源的?
唤醒的是处在锁池的entryList线程,owner线程执行完逻辑,准备释放锁前,将Entry List的head赋值给OnDeck,使其具备抢锁权限。

4wait(long)和wait()有啥区别?
Object类里面源码如下:

 public final void wait() throws InterruptedException {
        wait(0);
    }

native的wait方法我找了别人的截图(存疑,没去看源码):


JAVA线程间的状态转换_第5张图片
image.png

反正看了这么多,我最大的感受是,如果有时间有经历就看看源码吧,二道贩子的知识或多或少会有一些作者的认知在里面。

你可能感兴趣的:(JAVA线程间的状态转换)