Java 使用synchronizedMap的用户级锁定方法&;重入锁

Java 使用synchronizedMap的用户级锁定方法&;重入锁,java,concurrency,reentrantlock,Java,Concurrency,Reentrantlock,我想实现如下代码所示的基于用户的锁定 如果在用户“1234”(java.lang.String.class identifier)上发生了某个进程,或者某个进程正在使用该用户,则其他进程应等待该进程完成。听起来很简单,我尝试使用reeterrentlock使其工作,但我陷入了永久等待状态和死锁,尽管我即将使用synchronized块使其工作,但我想通过reeterrentlock实现这一点 下面是我的代码和日志 让我知道我做错了什么 //Research.java file public

我想实现如下代码所示的基于用户的锁定

如果在用户“1234”(java.lang.String.class identifier)上发生了某个进程,或者某个进程正在使用该用户,则其他进程应等待该进程完成。听起来很简单,我尝试使用
reeterrentlock
使其工作,但我陷入了永久等待状态和死锁,尽管我即将使用
synchronized
块使其工作,但我想通过
reeterrentlock
实现这一点

下面是我的代码和日志

让我知道我做错了什么

//Research.java file
  public static final int PERIOD = 10;
  public static final int END_INCLUSIVE = 23;
  public static final int N_THREADS = 8;

  public static void main(String[] args) {
    final ExecutorService executorService = Executors.newFixedThreadPool(N_THREADS);
    IntStream.rangeClosed(1, END_INCLUSIVE).forEach(value -> executorService.submit(() -> {
      final String user = value % 2 == 0 ? "1234" : "5678";
      process(value + "process", user);
    }));
    executorService.shutdown();
  }

  private static void process(String tag, String user) {
    System.out.println("waiting tag=" + tag + ",user=" + user + ",TH=" + Thread.currentThread().getName());
    AccountLock.getInstance().lock(user);
    System.out.println("in tag=" + tag + ",user=" + user + ",TH=" + Thread.currentThread().getName());
    sleep(tag, PERIOD);
    AccountLock.getInstance().unlock(user);
    System.out.println("out tag=" + tag + ",user=" + user + ",TH=" + Thread.currentThread().getName());
  }

  private static void sleep(String tag, long s) {
    boolean interrupt = false;
    try {
      TimeUnit.SECONDS.sleep(s);
    } catch (InterruptedException e) {
      interrupt = true;
      e.printStackTrace();
    } finally {
      if (interrupt) {
        Thread.currentThread().interrupt();
      }
    }
  }
如果您需要相同的线程转储,请告诉我


我在mac上使用java8

死锁是由地图和锁的交互产生的

更准确地说:您使用的是
Collections.syncrhonized
map,这实际上是对原始map实例的每个(大约)方法进行互斥

这意味着,例如,只要有人在
computeifassent
(或
computeIfPresent
)调用中,就不能在map实例上调用其他方法

到目前为止没有问题

除了,
computeIfxx
调用中,您可以通过获取锁实例来执行另一个锁操作

拥有两个不同的锁,而不保持严格的锁获取顺序,是造成死锁的最佳方法,您在这里演示了这一点

年表样本:

  • 线程T1进入映射(获取锁M),创建锁L1并获取它。T1保存M和L1
  • 线程T1存在于
    map.computeXX
    ,释放M.T1保存L1(仅限)
  • 线程T2进入map(获取M)。T1保持L1,T2保持M
  • 线程T2试图获取L1(与T1相同),但被T1阻止。T1保持L1,T2保持M并等待L1。你看到了吗
  • 线程T1已完成。它想要释放L1,所以尝试进入地图,这意味着尝试获取M,由T2持有。T1保持L1,等待M。T2保持M,等待L1。比赛结束了
  • 解决方案:不尝试创建锁并同时获取它是很有诱惑力的,例如,在锁定的方法中不获取锁。让我们试一下(只是想说明一点,你会看到这有多困难)

    如果你打破了这个嵌入的锁模式,当你的线程已经有了另一个锁时,你将永远不会有锁/解锁调用,从而防止任何死锁模式(死锁不能发生在一个可重入的锁上)

    这意味着:将AccountLock类从锁定实例更改为锁定工厂实例。或者,如果你想把东西整合起来,就换一种方式

    lock(String user) {
        LOCKS.computeIfAbsent(user, u -> new ReentrantLock()).lock();
    }
    
    通过将锁获取移到map的方法调用之外,我将其从map的互斥体中移除,并消除了死锁的可能性

    修正后,我创建了另一个bug

    在解锁端,使用返回null的
    computeIfPresent
    。这意味着一旦锁定完成,您将打算将其从地图中移除。这一切都很好,但它在解锁情况下创建了竞争条件

  • T1为用户1创建锁L1,将其放入映射并锁定
  • T2需要相同的L1,从映射中获取它,并等待它的可用性
  • T1完成,释放L1(T2还不知道),并将其从映射中移除
  • T3进入并希望为用户1锁定,但L1已从地图中消失,因此它创建L2并获取它
  • T2看到L1是自由的,获得它并工作
  • 此时,T2和T3都可以同时访问用户1,这是坏的
  • T2首先完成,转到map实例并释放用户1的锁,即L2,一个它从未获得过的锁。非法监视状态异常
  • 没有简单的办法摆脱这一点。您必须确保想要访问用户的并发线程具有一致的锁实例(这里的问题是T1释放锁,而T2保持锁,但尚未锁定锁)。这在您的原始代码中不可能发生,因为映射的互斥锁,但互斥锁会造成死锁

    那怎么办呢? 您永远无法从地图中删除关键点

    public void unlock(String user) {
        LOCK_MAP.computeIfPresent(user, (s, lock) -> {
            lock.unlock();
            return lock;
    });
    
    但是没有人会从地图上释放密钥,而地图会无限期地增长——可以添加一个线程安全计数器,在释放之前检查锁是否“被获取”。这会不会反过来产生另一个bug

    话虽如此,这里仍有一些需要加强的地方:

    • 锁释放应在最终块中执行
    • 您创建的锁实例数量是无限的。您可能希望避免这种情况(这是真正困难的部分)
    • 在地图级别使用互斥是一种浪费。ConcurrentHashMap将提供更细粒度的锁机制
    • 谁知道这里还剩下什么虫子
    结论 尝试并查看中提供的任何解决方案是否有助于您实现目标。使用受尊重的库实现可能更安全


    并发编程很难。其他人已经为您这样做了。

    看起来,当您第一次尝试锁定用户时,您只实例化了一个锁,而没有锁定它。(第一次:空映射。
    computeIfPresent
    因此不执行任何操作,
    computeifapsent
    创建锁,但不接受锁)。否则,Solomon可能是对的(尽管我猜
    睡眠
    是为了演示目的)。您也将面临一些无限制的内存使用。有关此模式的一些讨论:@GPI谢谢!链接非常有帮助,一些答案也有相同的建议,你能帮我识别这里的死锁吗?我已经更新了一点代码,非常感谢你们提供了详细的答案,我能够在这篇工作帖子上读到你们的答案。到目前为止还在工作,但我已经使用条纹锁将代码迁移到guava的条纹锁
    lock(String user) {
        LOCKS.computeIfAbsent(user, u -> new ReentrantLock()).lock();
    }
    
    public void unlock(String user) {
        LOCK_MAP.computeIfPresent(user, (s, lock) -> {
            lock.unlock();
            return lock;
    });