使用syncedMap和ReentrantLock的用户级别锁定方法

如何解决使用syncedMap和ReentrantLock的用户级别锁定方法

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

如果在用户“ 1234”(java.lang.String.class标识符)上正在发生某个进程,或者某个进程正在使用用户,则其他进程应等待该进程完成。听起来很简单,我尝试使用ReenterrentLock使它工作,但是我陷入了永久的等待状态和死锁,尽管我打算使用synchronised块使其工作,但我想通过ReenterrentLock

下面是我的代码和日志。

让我知道我在做错什么。

//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 + ",TH=" + Thread.currentThread().getName());
    sleep(tag,PERIOD);
    AccountLock.getInstance().unlock(user);
    System.out.println("out tag=" + tag + ",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();
      }
    }
  }
/**
 * AccountLock
 */
final class AccountLock {
  private static final Map<String,Lock> LOCK_MAP = Collections.synchronizedMap(new HashMap<>());
  private static volatile AccountLock INSTANCE;

  private AccountLock() {
  }

  public static AccountLock getInstance() {
    if (INSTANCE == null) {
      synchronized (AccountLock.class) {
        if (INSTANCE == null) {
          INSTANCE = new AccountLock();
        }
      }
    }
    return INSTANCE;
  }

  public void lock(String user) {
    LOCK_MAP.computeIfPresent(user,(s,lock) -> {
      lock.lock();
      return lock;
    });
    LOCK_MAP.computeIfAbsent(user,s -> {
      final ReentrantLock lock = new ReentrantLock(true);
      lock.lock();
      return lock;
    });
  }

  public void unlock(String user) {
    LOCK_MAP.computeIfPresent(user,lock) -> {
      lock.unlock();
      return null;
    });
  }
}
//logs
//waiting tag=2process,user=1234,TH=pool-1-thread-2
//waiting tag=3process,user=5678,TH=pool-1-thread-3
//waiting tag=1process,TH=pool-1-thread-1
//waiting tag=4process,TH=pool-1-thread-4
//waiting tag=5process,TH=pool-1-thread-5
//in tag=3process,TH=pool-1-thread-3
//in tag=4process,TH=pool-1-thread-4
//in tag=5process,TH=pool-1-thread-5
//waiting tag=6process,TH=pool-1-thread-6
//waiting tag=7process,TH=pool-1-thread-7
//waiting tag=8process,TH=pool-1-thread-8

让我知道您是否需要相同的线程转储。

我在Mac上使用的是Java8

解决方法

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

更精确的说:您使用Collections.syncrhonized映射,实际上,它会将原始映射实例的每个(或大约)方法互斥。

例如,这意味着只要有人在computeIfAbsent(或computeIfPresent)调用中,就无法在地图实例上调用其他方法。

到目前为止,没有问题。

除了内部 computeIfxx调用之外,您还可以通过获取锁定实例来进行另一次锁定操作。

拥有两个不同的锁并且不维护严格的锁获取顺序是解决死锁的完美方法,您将在此处进行演示。

样本年表:

  1. 线程T1进入映射(获取锁M),创建并获取锁L1。 T1拥有M和L1。
  2. 线程T1存在map.computeXX,释放M。T1保留L1(仅)。
  3. 线程T2进入地图(获取M)。 T1持有L1,T2持有M。
  4. 线程T2尝试获取L1(与T1相同),被T1阻塞。 T1保持L1,T2保持M并等待L1。你看到它来了吗?
  5. 线程T1完成。它想释放L1,因此尝试进入地图,这意味着试图获取M,该M由T2持有。 T1持有L1,等待M。T2持有M,等待L1。游戏结束。

解决方案:诱使您不要尝试创建锁并同时使用它,例如,处于锁定方法中时不要获取锁定。让我们尝试一下(只是指出一点,您会发现它变得多么困难)。

如果您打破了这种嵌入式锁定模式,则当线程已经具有另一个锁定时,您将永远不会进行锁定/解锁调用,从而避免了任何死锁模式(单个可重入锁定无法发生死锁)。

这意味着:将AccountLock类从锁定实例更改为锁定工厂实例。或者,如果您想集成东西,则以不同的方式进行。

lock(String user) {
    LOCKS.computeIfAbsent(user,u -> new ReentrantLock()).lock();
}

通过将锁获取移动到地图的方法调用之外,我将其从地图的互斥锁中删除,并且消除了死锁的可能性。

更正了这个错误之后,我创建了另一个错误。

在解锁方面,您使用computeIfPresent返回空值。这意味着一旦完成锁定,便打算将其从地图中删除。一切都很好,但是它会在解锁情况下产生竞争条件。

  1. T1为用户1创建锁L1,将其放入地图中并对其进行锁定。
  2. T2想要相同的L1,从地图上获取它,然后等待其可用性
  3. T1完成,释放L1(T2尚不知道),然后将其从地图上删除
  4. T3进入并希望为用户1锁定,但是L1不在地图上,因此它创建L2并获取它。
  5. T2看到L1是免费的,获得并工作了
  6. 在这一点上,T2和T3都可以同时访问用户1,这是
  7. T2首先完成,转到地图实例,并为用户1释放锁L2,这是它最初从未获得的锁。 IllegalMonitorStateException。

这是没有简单的方法。您必须确保要访问用户的并发线程具有一致的锁实例(这里的问题是T1释放了一个锁,而T2却保持了该锁,但尚未锁定它)。您的原始代码不会发生这种情况-由于地图的互斥锁,但是该互斥锁会产生死锁。

那该怎么办? 您永远无法从地图上删除按键。

public void unlock(String user) {
    LOCK_MAP.computeIfPresent(user,(s,lock) -> {
        lock.unlock();
        return lock;
});

但是,没有人从地图上释放无限期增长的密钥-一个人可以添加一个线程安全计数器,以在释放之前检查是否没有“获取”锁。那会不会又造成另一个错误?

可以这么说,这里还有几件事需要加强:

  • 锁定释放应在finally块中执行
  • 您创建的锁实例数量不受限制。您可能要避免这种情况(这是真正的困难部分)
  • 在地图级别具有互斥体是一种浪费。 ConcurrentHashMap将提供更精细的粒度锁定机制
  • 谁知道这里还剩下什么错误?

结论

尝试看看this other question中提供的任何解决方案是否可以帮助您实现目标。使用受尊重的库的实现可能更安全。

并发编程很难。其他人为您做了。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-