ConcurrentHashMap putIfAbsent первый раз

У меня есть ConcurrentHashMap и метод, который помещает String на карту, затем я выполняю некоторые действия в синхронизированном блоке на основе вставленного значения.

putIfAbsent возвращает предыдущее значение, связанное с указанным ключом, или null, если для ключа не было сопоставления — на основе официальной документации

Есть 2 действия, которые выполняются в зависимости от того, возвращает ли putIfAbsent значение null или нет.

Вот в чем хитрость. Я хочу, чтобы первое действие (когда 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");
           }           
      }
}

Если я вызываю этот метод с одним и тем же значением String из разных потоков login("some_id"); иногда (около 5% времени), я получаю это сообщение на консоли:

Second login
First login

Что мне нужно изменить, чтобы всегда быть уверенным, что First login выполняется первым?

Обновление: из того, что я прочитал, возможно ли, что logins.get(id) возвращает null , поэтому синхронизируется с нулевым объектом?


person Doua Beri    schedule 14.08.2016    source источник
comment
logins.putIfAbsent(id,id) и ваши операторы синхронизированного блока не являются атомарными. Вот почему иногда второй логин выполняется первым. Также не рекомендуется синхронизировать строковые литералы.   -  person Sandeep Reddy Goli    schedule 14.08.2016
comment
Должен ли map быть logins ?   -  person Michael Easter    schedule 14.08.2016
comment
@MichaelEaster да. извините, я изменил код   -  person Doua Beri    schedule 14.08.2016


Ответы (3)


иногда (около 5% времени) я получаю это сообщение на консоли:

У вас есть условие гонки, что первый добавленный не будет первым напечатанным.

В этом случае вашим основным узким местом является использование System.out, это контентный ресурс, который во много раз дороже, чем использование карты, параллельной или иной.

В этом случае вы также можете упростить свой код, чтобы получить только одну блокировку, которая является блокировкой 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
    }
}
person Peter Lawrey    schedule 14.08.2016

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");
           }           

}
}

Примечание. Также убедитесь, что вы удаляете записи из хэш-карт при удалении идентификатора.

или используйте другое поле (кроме идентификатора строки) для синхронизации кода

person Sandeep Reddy Goli    schedule 14.08.2016

Java предлагает другие механизмы синхронизации, которые обеспечивают большую степень детализации и, по-моему, ясность.

Рассмотрим код ниже. Код иллюстрирует (а) как защитить несколько операций с помощью блокировки (б) как разделы then и else могут обрабатываться по-разному (например, 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 ");
        }           
    }
}
person Michael Easter    schedule 15.08.2016