Prasyarat untuk kemunculan operasi atom

Mari kita lihat contoh ini untuk membantu anda memahami cara operasi atom berfungsi:

public class Counter {
    int count;

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

Apabila kami mempunyai satu utas, semuanya berfungsi dengan baik, tetapi jika kami menambah multithreading, kami mendapat hasil yang salah, dan semuanya kerana operasi kenaikan bukan satu operasi, tetapi tiga: permintaan untuk mendapatkan nilai semasamengira, kemudian naikkannya sebanyak 1 dan tulis semula kemengira.

Dan apabila dua utas ingin menambah pembolehubah, anda kemungkinan besar akan kehilangan data. Iaitu, kedua-dua utas menerima 100, akibatnya, kedua-duanya akan menulis 101 dan bukannya nilai yang dijangkakan 102.

Dan bagaimana untuk menyelesaikannya? Anda perlu menggunakan kunci. Kata kunci yang disegerakkan membantu menyelesaikan masalah ini, menggunakannya memberi anda jaminan bahawa satu utas akan mengakses kaedah pada satu masa.

public class SynchronizedCounterWithLock {
    private volatile int count;

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

Selain itu, anda perlu menambah kata kunci yang tidak menentu , yang memastikan keterlihatan rujukan yang betul antara urutan. Kami telah menyemak kerja beliau di atas.

Tetapi masih terdapat kelemahan. Yang terbesar ialah prestasi, pada masa itu apabila banyak utas cuba memperoleh kunci dan satu mendapat peluang menulis, selebihnya utas akan sama ada disekat atau digantung sehingga utas dilepaskan.

Semua proses ini, menyekat, bertukar kepada status lain adalah sangat mahal untuk prestasi sistem.

Operasi atom

Algoritma menggunakan arahan mesin peringkat rendah seperti compare-and-swap (CAS, compare-and-swap, yang memastikan integriti data dan sudah terdapat sejumlah besar penyelidikan mengenainya).

Operasi CAS biasa beroperasi pada tiga operan:

  • Ruang ingatan untuk bekerja (M)
  • Nilai jangkaan sedia ada (A) bagi pembolehubah
  • Nilai baharu (B) untuk ditetapkan

CAS mengemas kini M kepada B secara atom, tetapi hanya jika nilai M adalah sama dengan A, jika tidak, tiada tindakan diambil.

Dalam kes pertama dan kedua, nilai M akan dikembalikan. Ini membolehkan anda menggabungkan tiga langkah, iaitu, mendapatkan nilai, membandingkan nilai dan mengemas kininya. Dan semuanya bertukar menjadi satu operasi di peringkat mesin.

Sebaik sahaja aplikasi berbilang benang mengakses pembolehubah dan cuba mengemas kininya dan CAS digunakan, maka salah satu utas akan mendapatkannya dan boleh mengemas kininya. Tetapi tidak seperti kunci, utas lain hanya akan mendapat ralat tentang tidak dapat mengemas kini nilai. Kemudian mereka akan meneruskan kerja selanjutnya, dan penukaran dikecualikan sepenuhnya dalam jenis kerja ini.

Dalam kes ini, logik menjadi lebih sukar kerana hakikat bahawa kita perlu mengendalikan keadaan apabila operasi CAS tidak berjaya. Kami hanya akan memodelkan kod supaya ia tidak bergerak sehingga operasi berjaya.

Pengenalan kepada Jenis Atom

Pernahkah anda menemui situasi di mana anda perlu menyediakan penyegerakan untuk pembolehubah jenis int yang paling mudah ?

Cara pertama yang telah kami bincangkan ialah menggunakan volatile + synchronized . Tetapi terdapat juga kelas Atom* khas.

Jika kita menggunakan CAS, maka operasi berfungsi lebih cepat berbanding kaedah pertama. Selain itu, kami mempunyai kaedah khas dan sangat mudah untuk menambah nilai dan operasi kenaikan dan pengurangan.

AtomicBoolean , AtomicInteger , AtomicLong , AtomicIntegerArray , AtomicLongArray ialah kelas di mana operasinya adalah atom. Di bawah ini kami akan menganalisis kerja dengan mereka.

AtomicInteger

Kelas AtomicInteger menyediakan operasi pada nilai int yang boleh dibaca dan ditulis secara atom, selain menyediakan operasi atom lanjutan.

Ia telah mendapatkan dan menetapkan kaedah yang berfungsi seperti membaca dan menulis pembolehubah.

Iaitu, "berlaku-sebelum" dengan mana-mana penerimaan seterusnya bagi pembolehubah yang sama yang kita bincangkan sebelum ini. Kaedah atomic compareAndSet juga mempunyai ciri ketekalan memori ini.

Semua operasi yang mengembalikan nilai baharu dilakukan secara atom:

int addAndGet (int delta) Menambah nilai tertentu pada nilai semasa.
boolean compareAndSet(expected int, update int) Menetapkan nilai kepada nilai kemas kini yang diberikan jika nilai semasa sepadan dengan nilai yang dijangkakan.
int decrementAndGet() Mengurangkan nilai semasa sebanyak satu.
int getAndAdd(int delta) Menambah nilai yang diberikan kepada nilai semasa.
int getAndDecrement() Mengurangkan nilai semasa sebanyak satu.
int getAndIncrement() Meningkatkan nilai semasa sebanyak satu.
int getAndSet(int newValue) Menetapkan nilai yang diberikan dan mengembalikan nilai lama.
int incrementAndGet() Meningkatkan nilai semasa sebanyak satu.
lazySet(int newValue) Akhirnya tetapkan kepada nilai yang diberikan.
boolean weakCompareAndSet(dijangka, kemas kini int) Menetapkan nilai kepada nilai kemas kini yang diberikan jika nilai semasa sepadan dengan nilai yang dijangkakan.

Contoh:

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