1

王者并发课-铂金3:一劳永逸-如何理解锁的多次可重入问题 - 秦二爷

 1 year ago
source link: https://www.cnblogs.com/time-as-a-friend/p/16372094.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

欢迎来到《王者并发课》,本文是该系列文章中的第16篇

在前面的文章《铂金1:探本溯源-为何说Lock接口是Java中锁的基础》中,我们提到了锁的可重入问题,并作了简单介绍。鉴于锁的可重入是一个重要概念,所以本文把拿出来做一次单独讲解,以帮助你彻底理解它。

一、锁的可重入所造成的问题

首先,我们通过一段示例代码看锁的可重入是如何导致问题发生,以理解它的重要性。

public class ReentrantWildArea {
    // 野区锁定
    private boolean isAreaLocked = false;

    // 进入野区A
    public synchronized void enterAreaA() throws InterruptedException {
        isAreaLocked = true;
        System.out.println("已经进入野区A...");
        enterAreaB();
    }
    // 进入野区B
    public synchronized void enterAreaB() throws InterruptedException {
        while (isAreaLocked) {
            System.out.println("野区B方法进入等待中...");
            wait();
        }
        System.out.println("已经进入野区B...");
    }

    public synchronized void unlock() {
        isAreaLocked = false;
        notify();
    }
}

在上面这段代码中,我们创建了一片野区,包含了野区A野区B。接着,我们再创建一个打野英雄铠,让他进去野区打野,看看会发生什么事情。

public static void main(String[] args) {
  // 打野英雄铠进入野区
  Thread kaiThread = new Thread(() -> {
    ReentrantWildArea wildArea = new ReentrantWildArea();
    try {
      wildArea.enterAreaA();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  });
  kaiThread.start();
}

输出结果如下:

已经进入野区A...
野区B方法进入等待中...

从结果中可以看到,虽然在同一块野区,但是铠只进入野区A,却没能进入野区B,被阻塞在半道上了。从代码分析上看,野区的两个方法都声明了synchronized,但铠在进入野区A之后,野区进行了锁定isAreaLocked = true,导致铠进入野区B时失败。

这就是典型的锁的可重入所造成的问题。在并发编程时,如果未能处理好这一问题,将会造成线程的无限阻塞,其后果和死锁相当

二、理解锁的可重入

所谓锁的可重入,指的是锁可以被线程 重复递归 调用,也可以理解为对同一把锁的重复获取。 如果未能处理好锁的可重入问题,将会导致和死锁类似的问题。

d32832cfdf604ec4ab1962d603a9fce4~tplv-k3u1fbpfcp-zoom-1.image

三、如何避免锁的可重入问题

避免锁的可重入问题,需要注意两个方面:

  • 尽量避免编写需要重入获取锁的代码
  • 如果需要,使用可重入锁

在Java中,synchronized是可以重入的,下面的这段代码在调用时不会产生重入问题。

public class WildMonster {
    public synchronized void A() {
        B();
    }
    
    public synchronized void B() {
       doSomething...
    }
}

但是,基于Lock接口所实现的各种锁并不总是支持可重入的。在前面的文章中,我们已经展示过不支持重入的Lock接口实现。在具体的场景中使用时,需要务必注意这点。如果需要可重入锁,可以使用Java中的ReentrantLock类。

在本文中,我们再次介绍了锁的可重入问题,并介绍了其产生的原因及避免方式。Java中的synchronized关键字支持锁的可重入,但是其他显示锁并非总是支持这一特性,在使用时需要注意。

此外,需要注意的是,锁的可重入对锁的性能有一定的影响,而且实现起来更为复杂。所以,我们不能说锁的可重入与不可重入哪个好,这要取决于具体的问题

正文到此结束,恭喜你又上了一颗星✨

夫子的试炼

  • 查看ReentrantLock源码,了解其支持可重入的原理。

延伸阅读与参考资料

从业近十年,先后从事敏捷与DevOps咨询、Tech Leader和管理等工作,对分布式高并发架构有丰富的实战经验。热衷于技术分享和特定领域书籍翻译,掘金小册《高并发秒杀的设计精要与实现》作者。


关注公众号【MetaThoughts】,及时获取文章更新和文稿。

如果本文对你有帮助,欢迎点赞关注监督,我们一起从青铜到王者


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK