Есть определенные места, где групповые символы и параметры типа делают то же самое. Но есть также определенные места, где вам нужно использовать параметры типа.
Взяв ваш метод в качестве примера, предположим, что вы хотите убедиться, что список src
и dest
, переданный методу copy()
, должен быть одного и того же параметризованного типа, вы можете сделать это с помощью введите такие параметры:
public static void copy(List dest, List src)
Здесь вы убедитесь, что оба dest
и src
имеют одинаковый параметризованный тип для List
. Таким образом, можно скопировать элементы из src
в dest
.
Но если вы измените метод на использование подстановочного знака:
public static void copy(List extends Number> dest, List extends Number> src)
t работает должным образом. Во втором случае вы можете передать List
и List
как dest
и src
. Таким образом, перемещение элементов из src
в dest
больше не будет безопасным типом. Если вам не нужны такие отношения, вы можете вообще не использовать параметры типа.
. Другая разница между использованием подстановочных знаков и параметрами типа:
List
типа Integer
или суперкласс, вы можете сделать: public void print(List super Integer> list) // OK
, но вы не можете использовать параметр типа: public void print(List list) // Won't compile
Ссылки:
Используйте 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);
}
Я наткнулся на этот летний вопрос в поисках совета о том, как сделать то, что можно назвать «картой параллельного подсчета» - в частности, для поиска использования 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;
}
}
Скорость, возможно, не так важна для проблемы оригинального плаката, но другие применения такой «карты подсчета» могут выиграть от эффективности.
Прежде всего: не используйте хеш-таблицу! Он старый и очень медленный.
Дополнительно: синхронизация на более низком уровне не требуется, если вы уже синхронизируете на более высоком уровне (это верно и для 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: очень хорошо масштабируется
Против: Не интуитивно понятен, будет быстро сложным, не всегда возможно, много скрытых ловушек
Подход с блокировкой для каждого пользователя
Это создаст блокировки для разных пользователей, предполагая, что существует много разных пользователей. Вы можете создавать блокировки или мониторы с помощью некоторых небольших атомарных операций и блокировать их вместо полного списка.
Для этого небольшого примера это было бы излишним, но для очень сложных структур это может быть элегантным решением.
Судя по вашему коду, вашей синхронизации на _userSessions
должно быть достаточно, потому что вы не раскрываете ] Объекты AtomicInteger
.
Дополнительная безопасность, предлагаемая AtomicInteger
, в этом случае не нужна, поэтому по сути вы используете его здесь как изменяемое Integer
. Вы можете разместить вложенный статический класс, содержащий счетчик сеансов, как единственный атрибут на карте, если вас беспокоят дополнительные накладные расходы в AtomicInteger
(или немного уродливее: добавьте int [1] на карту как пока они не выставляются за пределами этого класса.)
Хорошая книга, я недавно прочитал ее сам.
В приведенном выше коде единственное замечание, которое у меня есть, это то, что AtomicInteger не нужен в синхронизированном блоке, но я сомневаюсь, что производительность будет быть заметным.
Лучший способ отследить производительность - это проверить ее. Настройте автоматизированный интегрированный нагрузочный тест для ключевых областей вашей платформы и отслеживайте производительность. Нагрузочный тест, если он содержит достаточно широкие временные окна и богатое использование рабочего процесса, может также выявить любые тупиковые ситуации, которые вы создали.
Хотя тупиковые ситуации могут показаться легкими, они могут легко проявиться в довольно простом рабочем процессе. шаблон.
Класс A блокирует ресурсы, затем вызывает B (может быть таким же простым, как получение / установка), который также блокирует ресурс. Другой поток вызывает B, который блокирует ресурсы, а затем вызывает A, вызывая взаимоблокировку.
При работе с богатой структурой полезно отобразить рабочий процесс, чтобы увидеть, как взаимодействуют классы. Возможно, вы сможете определить этот тип проблемы. Однако с действительно большими рамками они могут проскользнуть мимо. Лучшая защита, которую я нашел, - это изолировать блокировки на минимально возможной области и внимательно следить за вызовом вне класса, находясь внутри синхронизированного блока. Также помогает создание значительного количества нагрузочных тестов.