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包有很多平时开发中都非常有用的接口或者集合。花一点时间深入研究,加以利用,相信对我们的工作效果相信会有很大帮助。

如果你喜欢本文

长按二维码关注

相关推荐
<p> <span style="font-size:14px;color:#E53333;">限时福利1:</span><span style="font-size:14px;">购课进答疑群专享柳峰(刘运强)老师答疑服务</span> </p> <p> <br /> </p> <p> <br /> </p> <p> <span style="font-size:14px;"></span> </p> <p> <span style="font-size:14px;color:#337FE5;"><strong>为什么需要掌握高性能的MySQL实战?</strong></span> </p> <p> <span><span style="font-size:14px;"><br /> </span></span> <span style="font-size:14px;">由于互联网产品用户量大、高并发请求场景多,因此对MySQL的性能、可用性、扩展性都提出了很高的要求。使用MySQL解决大量数据以及高并发请求已经是程序员的必备技能,也是衡量一个程序员能力和薪资的标准之一。</span> </p> <p> <br /> </p> <p> <span style="font-size:14px;">为了让大家快速系统了解高性能MySQL核心知识全貌,我为你总结了</span><span style="font-size:14px;">「高性能 MySQL 知识框架图」</span><span style="font-size:14px;">,帮你梳理学习重点,建议收藏!</span> </p> <p> <br /> </p> <p> <img alt="" src="https://img-bss.csdnimg.cn/202006031401338860.png" /> </p> <p> <br /> </p> <p> <span style="font-size:14px;color:#337FE5;"><strong>【课程设计】</strong></span> </p> <p> <span style="font-size:14px;"><br /> </span> </p> <p> <span style="font-size:14px;">课程分为四大篇章,将为你建立完整的 MySQL 知识体系,同时将重点讲解 MySQL 底层运行原理、数据库的性能调优、高并发、海量业务处理、面试解析等。</span> </p> <p> <span style="font-size:14px;"><br /> </span> </p> <p> <span style="font-size:14px;"></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;"><strong>一、性能优化篇:</strong></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;">主要包括经典 MySQL 问题剖析、索引底层原理和事务与锁机制。通过深入理解 MySQL 的索引结构 B+Tree ,学员能够从根本上弄懂为什么有些 SQL 走索引、有些不走索引,从而彻底掌握索引的使用和优化技巧,能够避开很多实战中遇到的“坑”。</span> </p> <p style="text-align:justify;"> <br /> </p> <p style="text-align:justify;"> <span style="font-size:14px;"><strong>二、MySQL 8.0新特性篇:</strong></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;">主要包括窗口函数和通用表表达式。企业中的许多报表统计需求,如果不采用窗口函数,用普通的 SQL 语句是很难实现的。</span> </p> <p style="text-align:justify;"> <br /> </p> <p style="text-align:justify;"> <span style="font-size:14px;"><strong>三、高性能架构篇:</strong></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;">主要包括主从复制和读写分离。在企业的生产环境中,很少采用单台MySQL节点的情况,因为一旦单个节点发生故障,整个系统都不可用,后果往往不堪设想,因此掌握高可用架构的实现是非常有必要的。</span> </p> <p style="text-align:justify;"> <br /> </p> <p style="text-align:justify;"> <span style="font-size:14px;"><strong>四、面试篇:</strong></span> </p> <p style="text-align:justify;"> <span style="font-size:14px;">程序员获得工作的第一步,就是高效的准备面试,面试篇主要从知识点回顾总结的角度出发,结合程序员面试高频MySQL问题精讲精练,帮助程序员吊打面试官,获得心仪的工作机会。</span> </p>
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页