【问题标题】:ConcurrentHashMap putIfAbsent first timeConcurrentHashMap putIfAbsent 第一次
【发布时间】:2016-08-14 17:46:37
【问题描述】:

我有一个ConcurrentHashMap 和一个将字符串放入映射中的方法,然后我根据插入的值在同步块中执行一些操作。

putIfAbsent 返回与指定键关联的前一个值,如果该键没有映射,则返回 null - 基于官方文档

根据 putIfAbsent 是否返回 null 执行 2 个操作。

现在这是诀窍。我希望首先执行第一个操作(当 putIfAbsent 返回 null 时)并暂停所有其他线程。 我的代码 95% 的时间都按预期工作。

private final ConcurrentHashMap<String, String> logins = new ConcurrentHashMap<>();

public void login(String id){
      String inserted=logins.putIfAbsent(id,id);

      synchronized(logins.get(id)){
           if(inserted==null){
                System.out.println("First login");
           }else{
                System.out.println("Second login");
           }           
      }
}

如果我使用来自不同线程 login("some_id"); 的相同字符串值调用此方法,有时(大约 5% 的时间)我会在控制台上收到此消息:

Second login
First login

我需要进行哪些更改才能始终确保首先执行First login

更新:根据我的阅读,logins.get(id) 是否有可能返回 null ,因此在 null 对象上进行同步?

【问题讨论】:

  • logins.putIfAbsent(id,id) ,并且您的同步块语句不是原子的。这就是为什么有时会先执行第二次登录。此外,在字符串文字上同步也不是一个好主意
  • 应该maplogins 吗?
  • @MichaelEaster 是的。抱歉我修改了代码

标签: java java.util.concurrent concurrenthashmap concurrent-programming


【解决方案1】:

有时(大约 5% 的时间)我会在控制台上收到此消息:

您有一个竞争条件,即第一个添加的不是第一个打印的。

在这种情况下,您的主要瓶颈是使用 System.out,这是一个满足的资源,比使用 Map 贵很多倍,并发或其他方式。

在这种情况下,你也可以简化你的代码,这样你只获得一个锁,即System.out 上的锁,无论如何你都必须获得

// use System.out as lock so logging of actions is always in order.
private final Set<String> ids = Collections.newSetFromMap(new HashMap<>());

public void login(String id) {
    synchronized (System.out) {
        System.out.println(ids.add(id) ? "First login" : "Second login")l
    }
}

【讨论】:

    【解决方案2】:
    private final ConcurrentHashMap<String, String> logins= new ConcurrentHashMap<>();
    private ConcurrentHashMap<String, Object> locks= new ConcurrentHashMap<>();
    
    
    public void login(String id){
    
    locks.putIfAbsent(id,new Object());
    Object lock = locks.get(id);
    synchronized(lock)
    {
          String inserted=logins.putIfAbsent(id,id);
    
               if(inserted==null){
                    System.out.println("First login");
               }else{
                    System.out.println("Second login");
               }           
    
    }
    }
    

    注意:还要确保在删除 id 时从哈希图中删除条目

    或使用其他字段(除了字符串id)来同步代码

    【讨论】:

      【解决方案3】:

      Java 提供了其他同步机制,这些机制提供了更多粒度和 IMO 清晰度。

      考虑下面的代码。该代码说明 (a) 如何使用锁保护多个操作 (b) 如何以不同方式处理 thenelse 部分(例如,then 使用锁保护函数;else 假设函数不需要保护。根据您的情况进行更改):

      class Task implements Runnable {
          private String id;
          private ConcurrentHashMap<String,String> logins;
          private Lock lock;
      
          public Task(String id, ConcurrentHashMap<String,String> logins, Lock lock) {
              this.id = id;
              this.logins = logins;
              this.lock = lock;
          }
      
          public void run() {
              login(id);
          }
      
          public void login(String id){
              lock.lock();
      
              String inserted = logins.putIfAbsent(id,id);
      
              if (inserted==null) {
                  System.out.print("First login ");
                  // other functions that require synchronization
                  lock.unlock();
              } else {
                  lock.unlock();
                  // functions that do NOT require synchronization
                  System.out.print("Second login ");
              }           
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-05-31
        • 2015-12-26
        • 1970-01-01
        • 2020-11-24
        • 1970-01-01
        相关资源
        最近更新 更多