Ви конкретно запитуєте, як вони працюють всередині , тож ось ви:
Немає синхронізації
private int counter;
public int getNextUniqueIndex() {
return counter++;
}
В основному воно зчитує значення з пам'яті, збільшує його і повертає в пам'ять. Це працює в одному потоці, але сьогодні, в епоху багатоядерних, багатоядерних процесорів, багаторівневих кешів, він не працюватиме правильно. Перш за все, це вводить стан перегонів (декілька потоків можуть читати значення одночасно), але також проблеми із видимістю. Значення може зберігатися лише в " локальній " пам'яті процесора (деякий кеш) і не бути видимим для інших процесорів / ядер (і, таким чином, - потоків). Ось чому багато хто посилається на локальну копію змінної в потоці. Це дуже небезпечно. Розглянемо цей популярний, але зламаний код для зупинки потоку:
private boolean stopped;
public void run() {
while(!stopped) {
//do some work
}
}
public void pleaseStop() {
stopped = true;
}
Додати volatileдо stoppedзмінної, і це працює добре - якщо будь-який інший потік змінює stoppedзмінну за допомогою pleaseStop()методу, ви гарантовано побачите цю зміну негайно в while(!stopped)циклі робочої нитки . BTW - це не гарний спосіб переривання потоку, див. Як зупинити потік, який працює вічно, без будь-якого використання та Зупинення конкретного потоку Java .
AtomicInteger
private AtomicInteger counter = new AtomicInteger();
public int getNextUniqueIndex() {
return counter.getAndIncrement();
}
AtomicIntegerВикористовує клас CAS ( порівняння і заміни ) низькорівневих операцій ЦП (не потрібно ніякої синхронізації!) Вони дозволяють змінювати певну змінну , тільки якщо поточне значення одно що - то інше (і повертається успішно). Отже, коли ви виконуєте, getAndIncrement()він фактично працює в циклі (спрощена реальна реалізація):
int current;
do {
current = get();
} while(!compareAndSet(current, current + 1));
Так в основному: читати; намагайтеся зберігати збільшене значення; якщо не вдалося (значення більше не дорівнює current), прочитайте та повторіть спробу. compareAndSet()Реалізуються в машинному коді (збірка).
volatile без синхронізації
private volatile int counter;
public int getNextUniqueIndex() {
return counter++;
}
Цей код невірний. Він вирішує проблему з видимістю ( volatileгарантує, що інші потоки можуть бачити зміни, внесені до counter), але все ж має умову гонки. Це було пояснено неодноразово: до / після наростання не є атомним.
Єдиним побічним ефектом volatileє " промивання " кеш-пам'яті, щоб усі інші сторони бачили найсвіжішу версію даних. Це занадто суворо в більшості ситуацій; тому volatileне є типовим.
volatile без синхронізації (2)
volatile int i = 0;
void incIBy5() {
i += 5;
}
Та сама проблема, що і вище, але ще гірша, тому що iні private. Стан гонки все ще присутній. Чому це проблема? Якщо, скажімо, два потоки запускають цей код одночасно, вихід може бути + 5або + 10. Однак ви гарантовано побачите зміни.
Кілька незалежних synchronized
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
Дивно, але і цей код невірний. Насправді це абсолютно неправильно. Перш за все ви синхронізуєтесь i, що збирається змінити (до того ж, iце примітив, тому я думаю, що ви синхронізуєтесь на тимчасовому Integerствореному за допомогою автобоксингу ...) Повносправному. Ви також можете написати:
synchronized(new Object()) {
//thread-safe, SRSLy?
}
Жоден два потоки не можуть входити в один і той же synchronizedблок з тим же замком . У цьому випадку (і аналогічно у вашому коді) об'єкт блокування змінюється при кожному виконанні, тому synchronizedфактично не має ефекту.
Навіть якщо ви використовували остаточну змінну (або this) для синхронізації, код все одно є неправильним. Дві нитки можуть спочатку прочитати , iщоб tempсинхронно (мають однакове значення локально в temp), то перше привласнює нового значення i(скажімо, від 1 до 6) , а інший робить те ж саме (від 1 до 6).
Синхронізація повинна тривати від зчитування до присвоєння значення. Ваша перша синхронізація не впливає (читання intатома), а друга. На мою думку, це правильні форми:
void synchronized incIBy5() {
i += 5
}
void incIBy5() {
synchronized(this) {
i += 5
}
}
void incIBy5() {
synchronized(this) {
int temp = i;
i = temp + 5;
}
}