#java #multithreading #synchronization #locking #thread-synchronization
#java #многопоточность #синхронизация #блокировка #синхронизация потоков
Вопрос:
Просто хочу знать, чем отличаются приведенные ниже коды, которые выполняют ту же функциональность
Код 1:
class ReadWriteCounter {
ReadWriteLock lock = new ReentrantReadWriteLock();
private Integer count = 0;
public Integer incrementAndGetCount() {
lock.writeLock().lock();
try {
count = count 1;
return count;
} finally {
lock.writeLock().unlock();
}
}
public Integer getCount() {
lock.readLock().lock();
try {
return count;
} finally {
lock.readLock().unlock();
}
}
}
Код 2:
class ReadWriteCounter {
private Integer count = 0;
public getCount()
{
synchronized(count){
return count;
}
}
public void setCount(Integer i)
{
synchronized(count){
count = i;
}
}
}
Цель состоит в том, чтобы гарантировать, что при изменении count никакие другие потоки не обращаются к нему для чтения, и во время чтения никакие другие потоки не должны обращаться к нему для записи. Какое решение является оптимальным и почему? Кроме того, я буду использовать это в классе, где есть переменные поля, которые необходимо редактировать. Пожалуйста, предложите свои предложения.
Комментарии:
1. Это не ответ на вопрос, хотя использование метода AtomicInteger#incrementAndGet было бы альтернативой, менее подробной для этого варианта использования
Ответ №1:
ReentrantReadWriteLock — лучший способ реализовать ваши мысли. синхронизация разрешит только один поток, если два или более потоков попытаются прочитать количество. Но каждый может получить значение count, когда все они пытаются его прочитать.
Комментарии:
1. AtomicInteger — лучший способ в этом конкретном примере. Для большинства вариантов использования в пакете утилит параллелизма существуют существующие структуры данных, так что вам всегда следует дважды подумать, если вам действительно нужно напрямую обращаться к низкоуровневым элементам, таким как блокировки.
2. @Thilo Ты серьезно? Обратите внимание на эту часть проблемы: «Цель состоит в том, чтобы гарантировать, что при изменении count никакие другие потоки не обращаются к нему для чтения»; Вы получите старое значение, когда другой обновит «count», если вы используете AtomicInteger здесь. CAS в AtomicInteger не смог этого гарантировать.
3. @SeanLee В этом случае использования все должно быть в порядке в соответствии с AtomicInteger javadoc «AtomicInteger используется в таких приложениях, как счетчики с атомарным увеличением, и не может использоваться в качестве замены целого числа» и более подробная информация о CAS с atomics также предполагает, что CAS, использующие атомарные переменные, обычно достаточно хороши
Ответ №2:
Оба ваших решения работают, однако в том, как вы реализуете блокировку, есть ошибка.
Сначала разница в двух подходах: Блокировка ReentrantReadWriteLock в основном используется в ситуациях, когда у вас намного больше операций чтения, чем записи, обычно в соотношении 10 операций чтения: 1 запись. Это позволяет выполнять чтения одновременно, не блокируя друг друга, однако при запуске записи все чтения будут заблокированы. Таким образом, производительность является основной причиной.
Ошибка в вашем подходе: объект, на котором вы блокируете, должен быть окончательным. В setCount() вы эффективно заменяете объект, и это может привести к ошибочному чтению в это время.
Кроме того, никогда не предоставляйте объект, на котором вы блокируете. Объект, который вы блокируете, должен быть закрытым и окончательным. Причина в том, что если вы случайно предоставите объект, вызывающий объект может случайно использовать сам возвращаемый объект для блокировки, и в этом случае вы столкнетесь с проблемами конкуренции с компонентами за пределами самого этого класса.