Коли ми використовуємо AtomicReference
?
Чи потрібно для створення об'єктів у всіх багатопотокових програмах?
Наведіть простий приклад, коли слід використовувати AtomicReference.
Коли ми використовуємо AtomicReference
?
Чи потрібно для створення об'єктів у всіх багатопотокових програмах?
Наведіть простий приклад, коли слід використовувати AtomicReference.
Відповіді:
Атомна посилання повинна використовуватися в умовах, коли вам потрібно виконати прості атомні (тобто безпечні для потоків , нетривіальні) операції над посиланням, для яких синхронізація на основі монітора не є доцільною. Припустимо, ви хочете перевірити, чи є певне поле лише в тому випадку, якщо стан об'єкта залишається таким, яким ви востаннє перевіряли:
AtomicReference<Object> cache = new AtomicReference<Object>();
Object cachedValue = new Object();
cache.set(cachedValue);
//... time passes ...
Object cachedValueToUpdate = cache.get();
//... do some work to transform cachedValueToUpdate into a new version
Object newValue = someFunctionOfOld(cachedValueToUpdate);
boolean success = cache.compareAndSet(cachedValue,cachedValueToUpdate);
Через атомну еталонну семантику ви можете це зробити, навіть якщо cache
об'єкт ділиться між потоками, не використовуючи synchronized
. Взагалі вам краще використовувати синхронізатори або java.util.concurrent
рамки, а не голі, Atomic*
якщо ви не знаєте, що робите.
Дві відмінні посилання на мертве дерево, які ознайомлять вас із цією темою:
Зауважте, що (я не знаю, чи завжди це було правдою) призначення посилання (тобто =
) саме по собі атомне (оновлення примітивних 64-бітових типів на кшталт long
або double
не може бути атомним; але оновлення посилання завжди є атомним, навіть якщо це 64 біт ) без явного використання Atomic*
.
Дивіться специфікацію мови Java 3ed, розділ 17.7 .
AtomicReference
ви повинні позначити змінну, volatile
оскільки, хоча час виконання гарантує, що присвоєння посилань є атомним, компілятор може проводити оптимізацію за умови, що змінна не змінювалася іншими потоками.
AtomicReference
"; якщо ви будете використовувати його, то моя порада буде йти в напрямку , протилежної і позначити його , final
так що компілятор може оптимізувати відповідно.
Атомна посилання ідеально підходить для використання, коли вам потрібно поділити і змінити стан незмінного об'єкта між декількома потоками. Це надзвичайно щільне твердження, тому я його трохи розберу.
По-перше, незмінний об’єкт - це об'єкт, який фактично не змінюється після побудови. Найчастіше методи незмінного об'єкта повертають нові екземпляри цього ж класу. Деякі приклади включають класи обгортки Long і Double, а також String, щоб назвати лише декілька. (Згідно програмування Concurrency на JVM незмінні об'єкти є критичною частиною сучасної одночасності).
Далі, чому AtomicReference краще, ніж мінливий об’єкт для спільного використання спільного значення. Простий приклад коду покаже різницю.
volatile String sharedValue;
static final Object lock=new Object();
void modifyString(){
synchronized(lock){
sharedValue=sharedValue+"something to add";
}
}
Кожен раз, коли ви хочете змінити рядок, на який посилається це мінливе поле, виходячи з його поточного значення, спочатку потрібно отримати замок на цьому об'єкті. Це запобігає тим, що якийсь інший потік тим часом входить і змінює значення в середині нового конкатенації рядків. Потім, коли ваша нитка поновлюється, ви припиняєте роботу іншої нитки. Але якщо чесно, цей код буде працювати, він виглядає чисто, і це зробило б більшість людей щасливими.
Незначна проблема. Це повільно. Особливо, якщо існує багато суперечок цього об'єкта блокування. Це тому, що для більшості блокувань потрібен системний виклик ОС, і ваш потік блокується та буде вимкнений з процесора, щоб змінити місце для інших процесів.
Інший варіант - використовувати AtomicRefrence.
public static AtomicReference<String> shared = new AtomicReference<>();
String init="Inital Value";
shared.set(init);
//now we will modify that value
boolean success=false;
while(!success){
String prevValue=shared.get();
// do all the work you need to
String newValue=shared.get()+"lets add something";
// Compare and set
success=shared.compareAndSet(prevValue,newValue);
}
Тепер чому це краще? Чесно кажучи, цей код трохи менш чистий, ніж раніше. Але є щось дійсно важливе, що відбувається під кришкою в AtomicRefrence, і це порівняння та обмін. Переключення відбувається через одну інструкцію процесора, а не виклик ОС. Це єдина інструкція щодо процесора. А оскільки немає блокувань, немає контекстного перемикача у випадку, коли блокування виконується, що економить ще більше часу!
Улов - це для AtomicReferences не використовується виклик .equals (), а замість порівняння == для очікуваного значення. Тому переконайтеся, що очікуваний фактичний об’єкт, повернутий з get у циклі.
worked
отримати ту саму семантику, вам доведеться ввімкнути цикл .
Ось випадок використання AtomicReference:
Розглянемо цей клас, який діє як діапазон чисел, і використовує окремі змінні AtmomicInteger для підтримки нижньої та верхньої меж числа.
public class NumberRange {
// INVARIANT: lower <= upper
private final AtomicInteger lower = new AtomicInteger(0);
private final AtomicInteger upper = new AtomicInteger(0);
public void setLower(int i) {
// Warning -- unsafe check-then-act
if (i > upper.get())
throw new IllegalArgumentException(
"can't set lower to " + i + " > upper");
lower.set(i);
}
public void setUpper(int i) {
// Warning -- unsafe check-then-act
if (i < lower.get())
throw new IllegalArgumentException(
"can't set upper to " + i + " < lower");
upper.set(i);
}
public boolean isInRange(int i) {
return (i >= lower.get() && i <= upper.get());
}
}
І setLower, і setUpper є послідовністю "check-then-act", але вони не використовують достатнє блокування для того, щоб зробити їх атомними. Якщо діапазон чисел утримується (0, 10), і один потік викликає setLower (5), а інший потік викликає setUpper (4), з деяким невдалим терміном обидва пройдуть перевірки в сеттерах, і обидві модифікації будуть застосовані. Результатом є те, що діапазон тепер містить (5, 4) недійсний стан. Тому, хоча базові AtomicIntegers є безпечними для потоків, складний клас не є. Це можна виправити за допомогою AtomicReference замість використання окремих AtomicIntegers для верхньої та нижньої меж.
public class CasNumberRange {
// Immutable
private static class IntPair {
final int lower; // Invariant: lower <= upper
final int upper;
private IntPair(int lower, int upper) {
this.lower = lower;
this.upper = upper;
}
}
private final AtomicReference<IntPair> values =
new AtomicReference<IntPair>(new IntPair(0, 0));
public int getLower() {
return values.get().lower;
}
public void setLower(int lower) {
while (true) {
IntPair oldv = values.get();
if (lower > oldv.upper)
throw new IllegalArgumentException(
"Can't set lower to " + lower + " > upper");
IntPair newv = new IntPair(lower, oldv.upper);
if (values.compareAndSet(oldv, newv))
return;
}
}
public int getUpper() {
return values.get().upper;
}
public void setUpper(int upper) {
while (true) {
IntPair oldv = values.get();
if (upper < oldv.lower)
throw new IllegalArgumentException(
"Can't set upper to " + upper + " < lower");
IntPair newv = new IntPair(oldv.lower, upper);
if (values.compareAndSet(oldv, newv))
return;
}
}
}
Ви можете використовувати AtomicReference при застосуванні оптимістичних блокувань. У вас є спільний об’єкт, і ви хочете змінити його з більш ніж 1 потоку.
Оскільки інші потоки, можливо, змінили його та / можуть змінювати між цими двома кроками. Це потрібно робити в атомній операції. саме тут AtomicReference може допомогти
Ось дуже простий випадок використання і не має нічого спільного з безпекою різьби.
Щоб поділити об'єкт між викликами лямбда, AtomicReference
це варіант :
public void doSomethingUsingLambdas() {
AtomicReference<YourObject> yourObjectRef = new AtomicReference<>();
soSomethingThatTakesALambda(() -> {
yourObjectRef.set(youObject);
});
soSomethingElseThatTakesALambda(() -> {
YourObject yourObject = yourObjectRef.get();
});
}
Я не кажу, що це гарний дизайн чи що-небудь (це лише тривіальний приклад), але якщо у вас є випадок, коли вам потрібно поділитися об'єктом між викликами лямбда, AtomicReference
це варіант.
Насправді ви можете використовувати будь-який об’єкт, який містить посилання, навіть колекцію, що містить лише один предмет. Однак AtomicReference - це ідеально підходить.
Я не буду багато говорити. Вже мої шановні друзі-колеги дали свій цінний внесок. Повноцінний запущений код в останньому блозі повинен усунути будь-яку плутанину. Йдеться про невелику програму бронювання сидінь у фільмі в багатопотоковому сценарії.
Деякі важливі елементарні факти такі. 1> Різні потоки можуть протиставляти лише змінні, наприклад, статичні елементи в просторі купи. 2> Летючі читання або записування є повністю атомними і серіалізованими / відбувається раніше і відбувається лише з пам'яті. Кажучи це, я маю на увазі, що будь-яке прочитане буде слідувати попередньому запису в пам'яті. І будь-яке записування буде слідувати попередньому прочитаному з пам'яті. Тож будь-яка нитка, яка працює з летючою речовиною, завжди побачить найновіше значення. AtomicReference використовує цю властивість мінливих.
Нижче наведено деякі вихідні коди AtomicReference. AtomicReference відноситься до посилання на об'єкт. Ця посилання є змінною змінною членом в екземплярі AtomicReference, як показано нижче.
private volatile V value;
get () просто повертає останнє значення змінної (як летючі речовини роблять "відбувається раніше").
public final V get()
Далі йде найважливіший метод AtomicReference.
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
Метод CompareAndSet (очікувати, оновити) викликає метод сравнениеAndSwapObject () небезпечного класу Java. Цей метод виклику небезпечних посилається на власний виклик, який викликає одну інструкцію для процесора. "очікувати" та "оновлювати" кожну посилання на об'єкт.
Якщо і лише тоді, коли змінна члена екземпляра AtomicReference "значення" посилається на один і той же об'єкт, посилається на "очікувати", "оновлення" призначається цій змінній інстанції зараз, а "істина" повертається. Або ж повертається помилка. Вся справа робиться атомно. Жодна інша нитка не може перехоплюватися між ними. Оскільки це одна операція процесора (магія сучасної архітектури комп'ютера), це часто швидше, ніж використання синхронізованого блоку. Але пам’ятайте, що коли кілька змінних потрібно оновити атомно, AtomicReference не допоможе.
Я хотів би додати повноцінний запущений код, який можна запустити в затемнення. Це дозволило б очистити багато плутанини. Тут 22 користувачі (теми MyTh) намагаються забронювати 20 місць. Далі йде фрагмент коду, а потім повний код.
Фрагмент коду, де 22 користувачі намагаються забронювати 20 місць.
for (int i = 0; i < 20; i++) {// 20 seats
seats.add(new AtomicReference<Integer>());
}
Thread[] ths = new Thread[22];// 22 users
for (int i = 0; i < ths.length; i++) {
ths[i] = new MyTh(seats, i);
ths[i].start();
}
Далі йде повний запущений код.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class Solution {
static List<AtomicReference<Integer>> seats;// Movie seats numbered as per
// list index
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
seats = new ArrayList<>();
for (int i = 0; i < 20; i++) {// 20 seats
seats.add(new AtomicReference<Integer>());
}
Thread[] ths = new Thread[22];// 22 users
for (int i = 0; i < ths.length; i++) {
ths[i] = new MyTh(seats, i);
ths[i].start();
}
for (Thread t : ths) {
t.join();
}
for (AtomicReference<Integer> seat : seats) {
System.out.print(" " + seat.get());
}
}
/**
* id is the id of the user
*
* @author sankbane
*
*/
static class MyTh extends Thread {// each thread is a user
static AtomicInteger full = new AtomicInteger(0);
List<AtomicReference<Integer>> l;//seats
int id;//id of the users
int seats;
public MyTh(List<AtomicReference<Integer>> list, int userId) {
l = list;
this.id = userId;
seats = list.size();
}
@Override
public void run() {
boolean reserved = false;
try {
while (!reserved && full.get() < seats) {
Thread.sleep(50);
int r = ThreadLocalRandom.current().nextInt(0, seats);// excludes
// seats
//
AtomicReference<Integer> el = l.get(r);
reserved = el.compareAndSet(null, id);// null means no user
// has reserved this
// seat
if (reserved)
full.getAndIncrement();
}
if (!reserved && full.get() == seats)
System.out.println("user " + id + " did not get a seat");
} catch (InterruptedException ie) {
// log it
}
}
}
}
Коли ми використовуємо AtomicReference?
AtomicReference - гнучкий спосіб атомного оновлення змінної величини без використання синхронізації.
AtomicReference
підтримка безблокового програмування безпечного потоку на окремих змінних.
Існує кілька способів досягнення безпеки потоку за допомогою одночасного API високого рівня . Атомні змінні є одним із безлічі варіантів.
Lock
об'єкти підтримують ідіоми блокування, які спрощують багато паралельних програм.
Executors
визначити API високого рівня для запуску та управління потоками. Реалізації виконавця, що надаються java.util.concurrent, забезпечують управління пулом потоків, що підходить для масштабних програм.
Одночасні колекції полегшують управління великими колекціями даних і можуть значно зменшити потребу в синхронізації.
Атомні змінні мають функції, що мінімізують синхронізацію та допомагають уникнути помилок узгодженості пам'яті.
Наведіть простий приклад, коли слід використовувати AtomicReference.
Приклад коду з AtomicReference
:
String initialReference = "value 1";
AtomicReference<String> someRef =
new AtomicReference<String>(initialReference);
String newReference = "value 2";
boolean exchanged = someRef.compareAndSet(initialReference, newReference);
System.out.println("exchanged: " + exchanged);
Чи потрібно для створення об'єктів у всіх багатопотокових програмах?
Вам не доведеться використовувати AtomicReference
у всіх багатопотокових програмах.
Якщо ви хочете зберегти одну змінну, використовуйте AtomicReference
. Якщо ви хочете захистити блок коду, використовуйте інші конструкції, як Lock
/ synchronized
etc.
Ще один простий приклад - це зробити модифікацію безпечної нитки в об'єкті сеансу.
public PlayerScore getHighScore() {
ServletContext ctx = getServletConfig().getServletContext();
AtomicReference<PlayerScore> holder
= (AtomicReference<PlayerScore>) ctx.getAttribute("highScore");
return holder.get();
}
public void updateHighScore(PlayerScore newScore) {
ServletContext ctx = getServletConfig().getServletContext();
AtomicReference<PlayerScore> holder
= (AtomicReference<PlayerScore>) ctx.getAttribute("highScore");
while (true) {
HighScore old = holder.get();
if (old.score >= newScore.score)
break;
else if (holder.compareAndSet(old, newScore))
break;
}
}
Джерело: http://www.ibm.com/developerworks/library/j-jtp09238/index.html