Thread疾风传·螺旋丸还是须佐能乎
Posted Danny_姜
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Thread疾风传·螺旋丸还是须佐能乎相关的知识,希望对你有一定的参考价值。
在上一篇的 路漫漫其修远兮,吾将上下而求“锁” 中,我向大侠请教了几个线程相关的题目。可是很遗憾,始终没有寻得自己想要的结果。有不少小伙伴在看了文章之后,也对文中提到的问题表现出了一定的兴趣,所以就有了这一篇Thread疾风传--就当作是答疑解惑番外篇吧。
"锁"对象考察
第一题是通过对Synchronized的使用,考察其对Java中”锁“对象的理解。如图:
实际上,lock在SynchronizedTest类中是以非static成员变量的形式存在的,因此每创建一个Synchronized对象时,相应的,都会在堆内存中会创建新的lock对象。
虽然两个线程调用的是同样的startConditionLoop方法,但是synchronized(lock)中的lock是指向不同的内存地址,也就不是同一把锁,因此并不会存在互斥的作用。如下图:
但是,如果将lock改为static就不一样了,因为static在被创建在内存中的方法区,只会存在一个实例对象。因此即使是多个不同的线程,使用的锁都是指向内存中的同一地址,自然就有互斥作用了。如下图:
锁状态考察
第二题主要目的是想考察面试者对锁实现原理的理解,以及在不同并发级别下,锁相对应的状态。
其实主要是考察偏向锁、轻量级锁、重量级锁的理解
当代码执行到图1处时,表示目前只有1个线程在持有锁,还没有发生锁竞争事件,所以此时锁的状态是 偏向锁 状态。
当代码执行到图2处时,就表示有了第二个线程去申请锁对象,此时锁状态会被改写为 轻量级锁 状态。
当代码执行到图3时,就存在多个线程去竞争锁对象,此时锁状态会被进一步升级为 重量级锁 状态。
注意:需要注意的是锁状态只有升级操作,并不会降级。也就是说即使threadA、threadB和threadC的代码都已经执行完毕,lock的状态依然是重量级锁,后续如果继续有新的线程申请lock锁,同样还是会导致从用户态到内核态的转换。
多线程编程考察
前面2道题虽然涉及到些微代码,但是还是偏理论知识多一些,因此最后出了一道编程题。题目不算难,就是按照顺序依次执行同一个实例的3个方法。这道题实际上是可以在LeetCode上搜到的,如图:
文中提到的大侠给出的做法是使用Thread.sleep()来实现,虽然这种解法,在大多数情况下打印出的结果都是正确的。但是,理论上这样实现并不能百分百保证first一定执行在second之前,second也并不一定执行在third之前。简单来说,就是因为CPU分配线程执行片段是随机的。
记得我在第一次做这道题的时候,下意识想到的是使用Lock的Condition来实现,具体来说就是通过两个不同的Condition分别控制代码的等待机制,如下所示:
基本思路就是在执行second时,先调用c2.await方法进行等待,然后之后threadA执行完之后,才通过signal来通知threadB继续执行。
同样threadC也是要等threadB执行完之后,才通过signal唤醒执行。按照这种实现也确实能够使first、second、third按序执行。
但是这种实现方式同样存在致命问题:当我多次运行程序时,发现偶尔会存在只打印first日志,然后程序就处于卡住停滞状态,但是红色终止按钮并没有显示灰色,这就表示程序没有执行完毕,很显然死锁了!
一番调试下来,发现问题原因:假如threadA在threadB running之前就已经执行完毕,也就是thread.signal已经被触发,而threadB再去执行到running中的代码调用c1.await,此时不会再有任何线程去唤醒此等待操作,造成死锁!
因此需要一个判断机制,如果在执行threadB时判断threadA已经执行过了,则不需要执行等待逻辑。修改后的代码如下:
ReentrantLock lock = new ReentrantLock();
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();
private boolean firstPrinted = false;
private boolean secondPrinted = false;
private void testFooWithReentrantLock()
Thread t1 = new Thread(() ->
try
lock.lock();
foo.first();
firstPrinted = true;
c1.signal();
catch (Exception ignored) finally lock.unlock();
);
Thread t2 = new Thread(() ->
try
lock.lock();
c1.await();
if (!firstPrinted)
c1.await();
foo.second();
secondPrinted = true;
c2.signal();
catch (Exception ignored) finally lock.unlock();
);
Thread t3 = new Thread(() ->
try
lock.lock();
if (!secondPrinted)
c2.await();
c2.await();
foo.third();
catch (Exception ignored) finally lock.unlock();
);
t3.start();
t2.start();
t1.start();
虽说是能够实现效果了,但是对于上述实现方式,总感觉有点偏麻烦。所以事后也一直思考有么有更加简洁的方式实现。果不其然,Java中有一个API就能实现上述Condition + 变量控制的效果,就是Semphore。使用Semphore进行重构之后,代码更加简洁,如下:
最终效果是一样的,但是代码却比之前更加简练且易懂。实际上,在JUC包有很多平时开发中都非常有用的接口或者集合。花一点时间深入研究,加以利用,相信对我们的工作效果相信会有很大帮助。
如果你喜欢本文
长按二维码关注
以上是关于Thread疾风传·螺旋丸还是须佐能乎的主要内容,如果未能解决你的问题,请参考以下文章
C语言编程学习当鸣人放了一个螺旋丸,我突然发觉这个事情不简单......
LeetCode54 螺旋矩阵,题目不重要,重要的是这个技巧