Сколько потокобезопасность слишком много?

Есть определенные места, где групповые символы и параметры типа делают то же самое. Но есть также определенные места, где вам нужно использовать параметры типа.

  1. Если вы хотите применить некоторые отношения к различным типам аргументов метода, вы не можете сделать это с помощью подстановочных знаков, вы должны использовать параметры типа.

Взяв ваш метод в качестве примера, предположим, что вы хотите убедиться, что список src и dest, переданный методу copy(), должен быть одного и того же параметризованного типа, вы можете сделать это с помощью введите такие параметры:

public static  void copy(List dest, List src)

Здесь вы убедитесь, что оба dest и src имеют одинаковый параметризованный тип для List. Таким образом, можно скопировать элементы из src в dest.

Но если вы измените метод на использование подстановочного знака:

public static void copy(List dest, List src)

t работает должным образом. Во втором случае вы можете передать List и List как dest и src. Таким образом, перемещение элементов из src в dest больше не будет безопасным типом. Если вам не нужны такие отношения, вы можете вообще не использовать параметры типа.

. Другая разница между использованием подстановочных знаков и параметрами типа:

  • Если у вас есть только один аргумент с параметризованным типом, вы можете использовать подстановочный знак, хотя параметр типа также будет работать.
  • Параметры типа поддерживают множественные границы, подстановочные знаки - нет.
  • Поддержка подстановочных знаков как верхняя, так и нижняя границы, параметры типа просто поддерживают верхние границы. Итак, если вы хотите определить метод, который принимает List типа Integer или суперкласс, вы можете сделать:
    public void print(List list)  // OK
    
    , но вы не можете использовать параметр типа:
     public  void print(List list)  // Won't compile
    

Ссылки:

26
задан lukem00 1 December 2009 в 10:16
поделиться

5 ответов

Используйте ConcurrentHashMap, чтобы вы могли использовать putIfAbsent. Вам не нужно кодировать AtomicInteger для синхронизации.

   public final ConcurrentMap<String, AtomicInteger> userSessions =
       new ConcurrentHashMap<String, AtomicInteger>();

   public void registerUser(String userLogin) {
       AtomicInteger newCount = new AtomicInteger(1);
       AtomicInteger oldCount = userSessions.putIfAbsent(userLogin, newCount);
       if (oldCount != null) {
           oldCount.incrementAndGet();
       }
   }

   public void unregisterUser(String userLogin) {
       AtomicInteger sessionCount = userSessions.get(userLogin);
       if (sessionCount != null) {
           sessionCount.decrementAndGet();
       }
   }

   public boolean isUserRegistered(String userLogin) {
       AtomicInteger sessionCount = userSessions.get(userLogin);
       return sessionCount != null && sessionCount.intValue() > 0;
   }

Обратите внимание, это утечка ...

Попытка в версии без утечек:

   public final ConcurrentMap<String, Integer> userSessions =
       new ConcurrentHashMap<String, Integer>();

   public void registerUser(String userLogin) {
       for (;;) {
           Integer old = userSessions.get(userLogin);
           if (userSessions.replace(userLogin, old, old==null ? 1 : (old+1)) {
                break;
           }
       }
   }
   public void unregisterUser(String userLogin) {
       for (;;) {
           Integer old = userSessions.get(userLogin);
           if (old == null) {
               // Wasn't registered - nothing to do.
               break;
           } else if (old == 1) {
               // Last one - attempt removal.
               if (userSessions.remove(userLogin, old)) {
                   break;
               }
           } else {
               // Many - attempt decrement.
               if (userSessions.replace(userLogin, old, old-1) {
                   break;
               } 
           }
       }
   }
   public boolean isUserRegistered(String userLogin) {serLogin);
       return userSessions.containsKey(userLogin);
   }
14
ответ дан Tom Hawtin - tackline 25 September 2019 в 08:10
поделиться

Я наткнулся на этот летний вопрос в поисках совета о том, как сделать то, что можно назвать «картой параллельного подсчета» - в частности, для поиска использования ConcurrentHashMap с AtomicInteger.

Вот модифицированная версия ответа с наивысшим рейтингом , который использует AtomicInteger и не дает утечки. В моем (ограниченном) тестировании это кажется намного быстрее, чем версия Integer. Также отмечу, что использование ConcurrentMap.get() до ConcurrentMap.putIfAbsent(), похоже, экономит заметное время.

private final ConcurrentMap<String, AtomicInteger> userSessions =
   new ConcurrentHashMap<String, AtomicInteger>();

public void registerUser(String userLogin) {
    AtomicInteger oldCount = userSessions.get(key);
    if(oldCount!=null && getAndIncrementIfNonZero(oldCount)>0) return;
    AtomicInteger newCount = new AtomicInteger(1);
    while(true) {
        oldCount = userSessions.putIfAbsent(key, newCount);
        if(oldCount==null) return;
        if(getAndIncrementIfNonZero(oldCount)>0) return;
    }
}

public void unregisterUser(String userLogin) {
   AtomicInteger sessionCount = userSessions.get(userLogin);
   if (sessionCount != null) {
       int endingCount = sessionCount.decrementAndGet();
       if(endingCount==0) userSessions.remove(userLogin);
   }
}

public boolean isUserRegistered(String userLogin) {
   AtomicInteger sessionCount = userSessions.get(userLogin);
   return sessionCount != null && sessionCount.intValue() > 0;
}

private static int getAndIncrementIfNonZero(AtomicInteger ai) {
    while(true) {
        int current = ai.get();
        if(current==0) return 0;
        if(ai.compareAndSet(current, current+1)) return current;
    }
}

Скорость, возможно, не так важна для проблемы оригинального плаката, но другие применения такой «карты подсчета» могут выиграть от эффективности.

1
ответ дан Community 25 September 2019 в 08:10
поделиться

Прежде всего: не используйте хеш-таблицу! Он старый и очень медленный.
Дополнительно: синхронизация на более низком уровне не требуется, если вы уже синхронизируете на более высоком уровне (это верно и для AtomicInteger-thing).

Я вижу здесь разные подходы в зависимости от того, какой вариант использования здесь нужен .

Подход чтения / записи

Предполагая, что вы вызываете метод isUserRegistered очень часто, а другие методы только время от времени, хорошим способом является блокировка чтения-записи: это разрешено иметь несколько операций чтения одновременно, но только одну блокировку записи, чтобы управлять ими всеми (может быть получено, только если не требуется другой блокировки).

private static final Map<String, Integer> _userSessions =
  new HashMap<String, Integer>();

private ReadWriteLock rwLock =
  new ReentrantReadWriteLock(false); //true for fair locks

public static void registerUser(String userLogin) {
  Lock write = rwLock.writeLock();
  write.lock();
  try {
       Integer sessionCount = _userSessions.get(userLogin);
       if (sessionCount != null) {
           sessionCount = Integer.valueOf(sessionCount.inValue()+1);
       } else {
           sessionCount = Integer.valueOf(1)
       }
       _userSessions.put(userLogin, sessionCount);
   } finally {
     write.unlock();
   }
}

public static void unregisterUser(String userLogin) {
  Lock write = rwLock.writeLock();
  write.lock();
  try {
       Integer sessionCount = _userSessions.get(userLogin);
       if (sessionCount != null) {
           sessionCount = Integer.valueOf(sessionCount.inValue()-1);
       } else {
           sessionCount = Integer.valueOf(0)
       }
       _userSessions.put(userLogin, sessionCount);
   } finally {
     write.unlock();
   }
}

public static boolean isUserRegistered(String userLogin) {
  boolean result;

  Lock read = rwLock.readLock();
  read.lock();
  try {
       Integer sessionCount = _userSessions.get(userLogin);
       if (sessionCount != null) {
           result = sessionCount.intValue()>0
       } else {
           result = false;
       }
   } finally {
     read.unlock();
   }

   return false;
}

Pro: просто для понимания
Против: не масштабируется, если методы записи вызываются часто

Подход с использованием небольших атомарных операций

Идея состоит в том, чтобы делать небольшие шаги, которые все атомарны. В любом случае это приведет к очень хорошей производительности, но здесь много скрытых ловушек.

public final ConcurrentMap<String, AtomicInteger> userSessions =
   new ConcurrentHashMap<String, AtomicInteger>();
//There are other concurrent Maps for different use cases

public void registerUser(String userLogin) {
  AtomicInteger count;
  if (!userSession.containsKey(userLogin)){
    AtomicInteger newCount = new AtomicInteger(0);
    count = userSessions.putIfAbsent(userLogin, newCount);
    if (count == null){
      count=newCount;
    }
    //We need ifAbsent here, because another thread could have added it in the meantime
  } else {
    count = userSessions.get(userLogin);
  }
  count.incrementAndGet();
}

public void unregisterUser(String userLogin) {
  AtomicInteger sessionCount = userSessions.get(userLogin);
  if (sessionCount != null) {
    sessionCount.decrementAndGet();
  }
}

public boolean isUserRegistered(String userLogin) {
  AtomicInteger sessionCount = userSessions.get(userLogin);
  return sessionCount != null && sessionCount.intValue() > 0;
}

Pro: очень хорошо масштабируется
Против: Не интуитивно понятен, будет быстро сложным, не всегда возможно, много скрытых ловушек

Подход с блокировкой для каждого пользователя

Это создаст блокировки для разных пользователей, предполагая, что существует много разных пользователей. Вы можете создавать блокировки или мониторы с помощью некоторых небольших атомарных операций и блокировать их вместо полного списка.
Для этого небольшого примера это было бы излишним, но для очень сложных структур это может быть элегантным решением.

7
ответ дан 28 November 2019 в 07:56
поделиться

Судя по вашему коду, вашей синхронизации на _userSessions должно быть достаточно, потому что вы не раскрываете ] Объекты AtomicInteger .

Дополнительная безопасность, предлагаемая AtomicInteger , в этом случае не нужна, поэтому по сути вы используете его здесь как изменяемое Integer . Вы можете разместить вложенный статический класс, содержащий счетчик сеансов, как единственный атрибут на карте, если вас беспокоят дополнительные накладные расходы в AtomicInteger (или немного уродливее: добавьте int [1] на карту как пока они не выставляются за пределами этого класса.)

2
ответ дан 28 November 2019 в 07:56
поделиться

Хорошая книга, я недавно прочитал ее сам.

В приведенном выше коде единственное замечание, которое у меня есть, это то, что AtomicInteger не нужен в синхронизированном блоке, но я сомневаюсь, что производительность будет быть заметным.

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

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

Класс A блокирует ресурсы, затем вызывает B (может быть таким же простым, как получение / установка), который также блокирует ресурс. Другой поток вызывает B, который блокирует ресурсы, а затем вызывает A, вызывая взаимоблокировку.

При работе с богатой структурой полезно отобразить рабочий процесс, чтобы увидеть, как взаимодействуют классы. Возможно, вы сможете определить этот тип проблемы. Однако с действительно большими рамками они могут проскользнуть мимо. Лучшая защита, которую я нашел, - это изолировать блокировки на минимально возможной области и внимательно следить за вызовом вне класса, находясь внутри синхронизированного блока. Также помогает создание значительного количества нагрузочных тестов.

2
ответ дан 28 November 2019 в 07:56
поделиться