CodeGym/Java курс/Модул 3/Атомни операции в Java

Атомни операции в Java

На разположение

Предпоставки за възникване на атомните операции

Нека да разгледаме този пример, за да ви помогнем да разберете How работят атомарните операции:

public class Counter {
    int count;

    public void increment() {
        count++;
    }
}

Когато имаме една нишка, всичко работи чудесно, но ако добавим многопоточност, получаваме грешни резултати и всичко това, защото операцията за увеличаване не е една операция, а три: заявка за получаване на текущата стойностброя, след това го увеличете с 1 и запишете отново наброя.

И когато две нишки искат да увеличат променлива, най-вероятно ще загубите данни. Тоест и двете нишки получават 100, в резултат и двете ще напишат 101 instead of очакваната стойност от 102.

И How да го решим? Трябва да използвате ключалки. Ключовата дума synchronized помага за решаването на този проблем, като я използвате, ви дава гаранция, че една нишка ще има достъп до метода в даден момент.

public class SynchronizedCounterWithLock {
    private volatile int count;

    public synchronized void increment() {
        count++;
    }
}

Освен това трябва да добавите ключовата дума volatile , която гарантира правилната видимост на препратките сред нишките. Разгледахме работата му по-горе.

Но все пак има минуси. Най-голямата е производителността, в този момент, когато много нишки се опитват да придобият заключване и една получи възможност за запис, останалите нишки or ще бъдат блокирани, or преустановени, докато нишката бъде освободена.

Всички тези процеси, блокиране, преминаване към друго състояние са много скъпи за производителността на системата.

Атомни операции

Алгоритъмът използва машинни инструкции от ниско ниво, като например сравняване и размяна (CAS, сравняване и размяна, което гарантира целостта на данните и вече има голямо количество изследвания върху тях).

Типичната CAS операция работи с три операнда:

  • Пространство в паметта за работа (M)
  • Съществуваща очаквана стойност (A) на променлива
  • Нова стойност (B) трябва да бъде зададена

CAS атомарно актуализира M до B, но само ако стойността на M е същата като A, в противен случай не се предприема действие.

В първия и втория случай ще бъде върната стойността на M. Това ви позволява да комбинирате три стъпки, а именно получаване на стойността, сравняване на стойността и нейното актуализиране. И всичко това се превръща в една операция на ниво машина.

В момента, в който многонишково приложение получи достъп до променлива и се опита да я актуализира и се приложи CAS, тогава една от нишките ще го получи и ще може да го актуализира. Но за разлика от заключванията, други нишки просто ще получат грешки, че не могат да актуализират стойността. След това те ще преминат към по-нататъшна работа и превключването е напълно изключено при този вид работа.

В този случай логиката става по-трудна поради факта, че трябва да се справим със ситуацията, когато CAS операцията не работи успешно. Просто ще моделираме codeа, така че да не се движи, докато операцията не успее.

Въведение в атомните типове

Попадали ли сте в ситуация, в която трябва да настроите синхронизация за най-простата променлива от тип int ?

Първият начин, който вече разгледахме, е използването на volatile + synchronized . Но има и специални класове Atomic*.

Ако използваме CAS, тогава операциите работят по-бързо в сравнение с първия метод. Освен това имаме специални и много удобни методи за добавяне на стойност и операции за увеличаване и намаляване.

AtomicBoolean , AtomicInteger , AtomicLong , AtomicIntegerArray , AtomicLongArray са класове, в които операциите са атомарни. По-долу ще анализираме работата с тях.

Атомно цяло число

Класът AtomicInteger предоставя операции върху int стойност , която може да се чете и записва атомарно, в допълнение към предоставянето на разширени атомарни операции.

Има get и set методи , които работят като четене и писане на променливи.

Тоест „се случва преди“ с всяко следващо получаване на същата променлива, за която говорихме по-рано. Методът atomic compareAndSet също има тези функции за последователност на паметта.

Всички операции, които връщат нова стойност, се изпълняват атомарно:

int addAndGet (int делта) Добавя конкретна стойност към текущата стойност.
boolean compareAndSet(очаквано int, актуализиране int) Задава стойността на дадената актуализирана стойност, ако текущата стойност съвпада с очакваната стойност.
int decrementAndGet() Намалява текущата стойност с единица.
int getAndAdd(int делта) Добавя дадената стойност към текущата стойност.
int getAndDecrement() Намалява текущата стойност с единица.
int getAndIncrement() Увеличава текущата стойност с единица.
int getAndSet(int newValue) Задава дадената стойност и връща старата стойност.
int incrementAndGet() Увеличава текущата стойност с единица.
lazySet(int нова стойност) Накрая се настройва на зададената стойност.
boolean weakCompareAndSet(очаква се, актуализиране int) Задава стойността на дадената актуализирана стойност, ако текущата стойност съвпада с очакваната стойност.

Пример:

ExecutorService executor = Executors.newFixedThreadPool(5);
IntStream.range(0, 50).forEach(i -> executor.submit(atomicInteger::incrementAndGet));
executor.shutdown();
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.HOURS);

System.out.println(atomicInteger.get()); // prints 50
1
Задача
Модул 3,  нивоурок
Заключено
Earn a Million!
task4201
1
Задача
Модул 3,  нивоурок
Заключено
Early Bird Gets the Worm
task4202
Коментари
  • Популярен
  • Нов
  • Стар
Трябва да сте влезли, за да оставите коментар
Тази страница все още няма коментари