Приведите Java код с ошибкой синхронизации: public class Counter{ private int c=0; public void inc(){ c++; } } — объясните проблему в многопоточном доступе и предложите несколько корректных вариантов реализации счётчика с высокой производительностью
Ошибка (пример с вашей реализацией): public class Counter { private int c = 0; public void inc() { c++; } public int get() { return c; } } Проблема (кратко): операция c++c++c++ не атомарна — это чтение, инкремент и запись. При двух потоках возможна интерливинг: оба читают c=0c=0c=0, оба вычисляют 111 и оба записывают 111 — в итоге получаем 111 вместо ожидаемых 222. volatile здесь не поможет, потому что не делает сложные операции атомарными. Корректные варианты реализации (с упором на производительность): 1) Синхронизированный счётчик (корректно, но менее производительно при высокой конкуренции): public class CounterSync { private int c = 0; public synchronized void inc() { c++; } public synchronized int get() { return c; } } 2) AtomicInteger (CAS — хорош для умеренной конкуренции): import java.util.concurrent.atomic.AtomicInteger; public class CounterAtomic { private final AtomicInteger c = new AtomicInteger(0); public void inc() { c.incrementAndGet(); } public int get() { return c.get(); } } 3) LongAdder (лучше при высокой конкуренции по операциям инкремента; чтение — суммирование шардов): import java.util.concurrent.atomic.LongAdder; public class CounterLongAdder { private final LongAdder c = new LongAdder(); public void inc() { c.increment(); } public long get() { return c.sum(); } } 4) AtomicLong / LongAccumulator (если нужен long или сложная функция агрегации): import java.util.concurrent.atomic.AtomicLong; public class CounterAtomicLong { private final AtomicLong c = new AtomicLong(0L); public void inc() { c.incrementAndGet(); } public long get() { return c.get(); } } Краткие рекомендации: - Для низкой конкуренции — AtomicInteger/AtomicLong (CAS) обычно достаточно. - Для очень высокой конкуренции по инкременту — LongAdder даёт лучшую пропускную способность. - Для атомарных комплексных операций используйте синхронизацию или специализированные механизмы (CAS-loop, StampedLock и т.п.).
public class Counter {
private int c = 0;
public void inc() { c++; }
public int get() { return c; }
}
Проблема (кратко): операция c++c++c++ не атомарна — это чтение, инкремент и запись. При двух потоках возможна интерливинг: оба читают c=0c=0c=0, оба вычисляют 111 и оба записывают 111 — в итоге получаем 111 вместо ожидаемых 222. volatile здесь не поможет, потому что не делает сложные операции атомарными.
Корректные варианты реализации (с упором на производительность):
1) Синхронизированный счётчик (корректно, но менее производительно при высокой конкуренции):
public class CounterSync {
private int c = 0;
public synchronized void inc() { c++; }
public synchronized int get() { return c; }
}
2) AtomicInteger (CAS — хорош для умеренной конкуренции):
import java.util.concurrent.atomic.AtomicInteger;
public class CounterAtomic {
private final AtomicInteger c = new AtomicInteger(0);
public void inc() { c.incrementAndGet(); }
public int get() { return c.get(); }
}
3) LongAdder (лучше при высокой конкуренции по операциям инкремента; чтение — суммирование шардов):
import java.util.concurrent.atomic.LongAdder;
public class CounterLongAdder {
private final LongAdder c = new LongAdder();
public void inc() { c.increment(); }
public long get() { return c.sum(); }
}
4) AtomicLong / LongAccumulator (если нужен long или сложная функция агрегации):
import java.util.concurrent.atomic.AtomicLong;
public class CounterAtomicLong {
private final AtomicLong c = new AtomicLong(0L);
public void inc() { c.incrementAndGet(); }
public long get() { return c.get(); }
}
Краткие рекомендации:
- Для низкой конкуренции — AtomicInteger/AtomicLong (CAS) обычно достаточно.
- Для очень высокой конкуренции по инкременту — LongAdder даёт лучшую пропускную способность.
- Для атомарных комплексных операций используйте синхронизацию или специализированные механизмы (CAS-loop, StampedLock и т.п.).