Penggunaan praktis untuk AtomicInteger

230

Saya agak mengerti bahwa AtomicInteger dan variabel Atom lainnya memungkinkan akses bersamaan. Dalam kasus apa kelas ini biasanya digunakan?

James P.
sumber

Jawaban:

190

Ada dua kegunaan utama AtomicInteger:

  • Sebagai penghitung atom ( incrementAndGet(), dll) yang dapat digunakan oleh banyak utas secara bersamaan

  • Sebagai primitif yang mendukung instruksi compare-and-swap ( compareAndSet()) untuk mengimplementasikan algoritma non-blocking.

    Berikut adalah contoh generator nomor acak non-blocking dari Java Concurrency In Practice Brian Göetz :

    public class AtomicPseudoRandom extends PseudoRandom {
        private AtomicInteger seed;
        AtomicPseudoRandom(int seed) {
            this.seed = new AtomicInteger(seed);
        }
    
        public int nextInt(int n) {
            while (true) {
                int s = seed.get();
                int nextSeed = calculateNext(s);
                if (seed.compareAndSet(s, nextSeed)) {
                    int remainder = s % n;
                    return remainder > 0 ? remainder : remainder + n;
                }
            }
        }
        ...
    }

    Seperti yang Anda lihat, ini pada dasarnya bekerja dengan cara yang hampir sama incrementAndGet(), tetapi melakukan perhitungan sewenang-wenang ( calculateNext()) alih-alih kenaikan (dan memproses hasilnya sebelum kembali).

axtavt
sumber
1
Saya pikir saya mengerti penggunaan pertama. Ini untuk memastikan penghitung telah bertambah sebelum atribut diakses lagi. Benar? Bisakah Anda memberikan contoh singkat untuk penggunaan kedua?
James P.
8
Pemahaman Anda tentang penggunaan pertama agak benar - itu hanya memastikan bahwa jika utas lain memodifikasi penghitung antara operasi readdan write that value + 1, ini terdeteksi daripada menimpa pembaruan lama (menghindari masalah "pembaruan hilang"). Ini sebenarnya adalah kasus khusus compareAndSet- jika nilai lama adalah 2, kelas sebenarnya memanggil compareAndSet(2, 3)- jadi jika utas lain telah mengubah nilai sementara itu, metode kenaikan secara efektif restart dari awal.
Andrzej Doyle
3
"sisa> 0? sisa: sisa + n;" dalam ungkapan ini apakah ada alasan untuk menambahkan sisanya ke n ketika 0?
sandeepkunkunuru
101

Contoh paling sederhana mutlak yang dapat saya pikirkan adalah membuat penambahan operasi atom.

Dengan int standar:

private volatile int counter;

public int getNextUniqueIndex() {
    return counter++; // Not atomic, multiple threads could get the same result
}

Dengan AtomicInteger:

private AtomicInteger counter;

public int getNextUniqueIndex() {
    return counter.getAndIncrement();
}

Yang terakhir adalah cara yang sangat sederhana untuk melakukan efek mutasi sederhana (terutama penghitungan, atau pengindeksan unik), tanpa harus menggunakan sinkronisasi semua akses.

Logika bebas sinkronisasi yang lebih kompleks dapat digunakan dengan menggunakan compareAndSet()jenis penguncian optimis - dapatkan nilai saat ini, hitung hasil berdasarkan ini, tetapkan hasil ini jika nilai masih input yang digunakan untuk melakukan perhitungan, lain mulai lagi - tetapi menghitung contoh sangat berguna, dan saya akan sering menggunakan AtomicIntegerspenghitungan dan generator unik VM-lebar jika ada beberapa thread terlibat, karena mereka sangat mudah untuk bekerja dengan saya hampir menganggap itu optimasi prematur untuk menggunakan polos ints.

Meskipun Anda hampir selalu dapat mencapai jaminan sinkronisasi yang sama dengan intsdan synchronizeddeklarasi yang sesuai , keindahan dari AtomicIntegerthread-safety dibangun ke dalam objek aktual itu sendiri, daripada Anda perlu khawatir tentang kemungkinan interleavings, dan monitor yang dimiliki, dari setiap metode itu terjadi untuk mengakses intnilai. Jauh lebih sulit untuk secara tidak sengaja melanggar keamanan threads saat menelepon getAndIncrement()daripada ketika kembali i++dan mengingat (atau tidak) untuk mendapatkan set monitor yang benar sebelumnya.

Andrzej Doyle
sumber
2
Terima kasih atas penjelasan yang jelas ini. Apa keuntungan menggunakan AtomicInteger di atas kelas di mana semua metode disinkronkan? Apakah yang terakhir dianggap "lebih berat"?
James P.
3
Dari perspektif saya, ini terutama enkapsulasi yang Anda dapatkan dengan AtomicIntegers - sinkronisasi terjadi tepat pada apa yang Anda butuhkan, dan Anda mendapatkan metode deskriptif yang ada di API publik untuk menjelaskan apa hasil yang dimaksud. (Ditambah sampai batas tertentu Anda benar, sering orang akhirnya hanya akan menyinkronkan semua metode dalam kelas yang cenderung terlalu kasar, meskipun dengan HotSpot melakukan optimasi kunci dan aturan terhadap optimasi prematur, saya menganggap keterbacaan sebagai manfaat lebih besar daripada kinerja.)
Andrzej Doyle
Ini penjelasan yang sangat jelas dan tepat, Terima kasih !!
Akash5288
Akhirnya sebuah penjelasan yang benar untuk saya.
Benny Bottema
58

Jika Anda melihat metode yang dimiliki AtomicInteger, Anda akan melihat bahwa mereka cenderung sesuai dengan operasi umum pada int. Misalnya:

static AtomicInteger i;

// Later, in a thread
int current = i.incrementAndGet();

adalah versi yang aman untuk ini:

static int i;

// Later, in a thread
int current = ++i;

Metode peta seperti ini:
++iis i.incrementAndGet()
i++is i.getAndIncrement()
--iis i.decrementAndGet()
i--is i.getAndDecrement()
i = xis i.set(x)
x = iisx = i.get()

Ada metode kenyamanan lain juga, seperti compareAndSetatauaddAndGet

Powerlord
sumber
37

Penggunaan utama AtomicIntegeradalah ketika Anda berada dalam konteks multithreaded dan Anda perlu melakukan operasi aman thread pada integer tanpa menggunakan synchronized. Penugasan dan pengambilan pada tipe primitif intsudah bersifat atomik tetapi AtomicIntegerdilengkapi dengan banyak operasi yang bukan atomik int.

Yang paling sederhana adalah getAndXXXatau xXXAndGet. Misalnya getAndIncrement()adalah setara atom i++yang bukan atom karena sebenarnya merupakan jalan pintas untuk tiga operasi: pengambilan, penambahan dan penugasan. compareAndSetsangat berguna untuk mengimplementasikan semaphores, kunci, kait, dll.

Menggunakan AtomicIntegerlebih cepat dan lebih mudah dibaca daripada melakukan sinkronisasi menggunakan yang sama.

Tes sederhana:

public synchronized int incrementNotAtomic() {
    return notAtomic++;
}

public void performTestNotAtomic() {
    final long start = System.currentTimeMillis();
    for (int i = 0 ; i < NUM ; i++) {
        incrementNotAtomic();
    }
    System.out.println("Not atomic: "+(System.currentTimeMillis() - start));
}

public void performTestAtomic() {
    final long start = System.currentTimeMillis();
    for (int i = 0 ; i < NUM ; i++) {
        atomic.getAndIncrement();
    }
    System.out.println("Atomic: "+(System.currentTimeMillis() - start));
}

Pada PC saya dengan Java 1.6 tes atom berjalan dalam 3 detik sementara yang disinkronkan berjalan dalam sekitar 5,5 detik. Masalahnya di sini adalah bahwa operasi untuk menyinkronkan ( notAtomic++) sangat singkat. Jadi biaya sinkronisasi sangat penting dibandingkan dengan operasi.

Di samping atomicity, AtomicInteger dapat digunakan sebagai versi yang bisa berubah Integermisalnya dalam Mapnilai s.

gabuzo
sumber
1
Saya tidak berpikir saya ingin menggunakan AtomicIntegersebagai kunci peta, karena menggunakan equals()implementasi default , yang hampir pasti bukan semantik yang Anda harapkan jika digunakan dalam peta.
Andrzej Doyle
1
@Andrzej yakin, bukan sebagai kunci yang diperlukan untuk tidak dapat diubah, melainkan suatu nilai.
gabuzo
@ gabuzo Tahu mengapa integer atom berkinerja lebih baik dari yang disinkronkan?
Supun Wijerathne
Tesnya sudah cukup tua sekarang (lebih dari 6 tahun) mungkin menarik untuk diuji ulang dengan JRE baru-baru ini. Saya tidak masuk cukup dalam di AtomicInteger untuk menjawab tetapi karena ini adalah tugas yang sangat spesifik itu akan menggunakan teknik sinkronisasi yang hanya bekerja dalam kasus spesifik ini. Juga keberatan bahwa tes ini monothreaded dan membuat tes serupa di lingkungan yang penuh muatan mungkin tidak memberikan kemenangan yang jelas bagi AtomicInteger
gabuzo
Saya percaya ini 3 ms dan 5,5 ms
Sathesh
18

Sebagai contoh, saya memiliki perpustakaan yang menghasilkan instance dari beberapa kelas. Masing-masing instance harus memiliki ID integer unik, karena instance ini mewakili perintah yang dikirim ke server, dan setiap perintah harus memiliki ID unik. Karena beberapa utas diizinkan untuk mengirim perintah secara bersamaan, saya menggunakan AtomicInteger untuk menghasilkan ID tersebut. Pendekatan alternatif adalah dengan menggunakan semacam kunci dan integer biasa, tapi itu lebih lambat dan kurang elegan.

Sergei Tachenov
sumber
Terima kasih telah membagikan contoh praktis ini. Ini kedengarannya seperti sesuatu yang harus saya gunakan karena saya harus memiliki id unik untuk setiap file yang saya impor ke dalam program saya :)
James P.
7

Seperti kata gabuzo, kadang-kadang saya menggunakan AtomicIntegers ketika saya ingin melewatkan int dengan referensi. Ini adalah kelas bawaan yang memiliki kode khusus arsitektur, jadi lebih mudah dan cenderung lebih dioptimalkan daripada MutableInteger yang bisa saya kodekan dengan cepat. Yang mengatakan, rasanya seperti penyalahgunaan kelas.

David Ehrmann
sumber
7

Di Java 8, kelas atom telah diperluas dengan dua fungsi menarik:

  • int getAndUpdate (IntUnaryOperator updateFunction)
  • int updateAndGet (IntUnaryOperator updateFunction)

Keduanya menggunakan fungsi update untuk melakukan pembaruan nilai atom. Perbedaannya adalah bahwa yang pertama mengembalikan nilai lama dan yang kedua mengembalikan nilai baru. Fungsi pemutakhiran dapat diterapkan untuk melakukan operasi "bandingkan dan set" yang lebih kompleks daripada yang standar. Sebagai contoh ia dapat memeriksa bahwa penghitung atom tidak mencapai di bawah nol, biasanya itu akan memerlukan sinkronisasi, dan di sini kode bebas dari kunci:

    public class Counter {

      private final AtomicInteger number;

      public Counter(int number) {
        this.number = new AtomicInteger(number);
      }

      /** @return true if still can decrease */
      public boolean dec() {
        // updateAndGet(fn) executed atomically:
        return number.updateAndGet(n -> (n > 0) ? n - 1 : n) > 0;
      }
    }

Kode diambil dari Java Atomic Contoh .

pwojnowski
sumber
5

Saya biasanya menggunakan AtomicInteger ketika saya harus memberikan Id ke objek yang dapat diakses atau dibuat dari beberapa utas, dan saya biasanya menggunakannya sebagai atribut statis pada kelas yang saya akses di konstruktor objek.

DguezTorresEmmanuel
sumber
4

Anda dapat menerapkan kunci non-pemblokiran menggunakan compareAndSwap (CAS) pada bilangan bulat atom atau panjang. The "TL2" Software Transaksional Memori kertas menggambarkan ini:

Kami mengaitkan kunci-tulis versi khusus dengan setiap lokasi memori yang ditransaksikan. Dalam bentuknya yang paling sederhana, kunci-tulis versi adalah spinlock kata tunggal yang menggunakan operasi CAS untuk mendapatkan kunci dan toko untuk melepaskannya. Karena satu hanya membutuhkan bit tunggal untuk menunjukkan bahwa kunci diambil, kami menggunakan sisa kata kunci untuk menyimpan nomor versi.

Apa yang digambarkannya adalah pertama membaca bilangan bulat atom. Bagi ini menjadi bit kunci yang diabaikan dan nomor versi. Mencoba untuk CAS menulisnya sebagai bit kunci dibersihkan dengan nomor versi saat ini ke set bit kunci dan nomor versi berikutnya. Ulangi sampai Anda berhasil dan Anda adalah utas yang memiliki kunci. Buka kunci dengan mengatur nomor versi saat ini dengan kunci-bit dihapus. Makalah ini menjelaskan penggunaan nomor versi dalam kunci untuk mengoordinasikan bahwa utas memiliki rangkaian pembacaan yang konsisten ketika mereka menulis.

Artikel ini menjelaskan bahwa prosesor memiliki dukungan perangkat keras untuk membandingkan dan menukar operasi yang membuatnya sangat efisien. Ia juga mengklaim:

non-blocking counter berbasis CAS menggunakan variabel atom memiliki kinerja yang lebih baik daripada counter berbasis kunci dalam pertentangan rendah hingga sedang

simbo1905
sumber
3

Kuncinya adalah mereka memungkinkan akses bersamaan dan modifikasi dengan aman. Mereka umumnya digunakan sebagai penghitung di lingkungan multithreaded - sebelum pengenalan mereka ini harus menjadi kelas tertulis pengguna yang membungkus berbagai metode dalam blok yang disinkronkan.

Michael Berry
sumber
Saya melihat. Apakah ini dalam kasus di mana atribut atau instance bertindak sebagai semacam variabel global di dalam aplikasi. Atau ada kasus lain yang bisa Anda pikirkan?
James P.
1

Saya menggunakan AtomicInteger untuk memecahkan masalah Dining Philosopher.

Dalam solusi saya, contoh AtomicInteger digunakan untuk mewakili garpu, ada dua yang diperlukan per filsuf. Setiap filsuf diidentifikasi sebagai bilangan bulat, 1 sampai 5. Ketika garpu digunakan oleh seorang filsuf, AtomicInteger memegang nilai filsuf, 1 sampai 5, jika garpu tidak digunakan sehingga nilai AtomicInteger adalah -1 .

AtomicInteger kemudian memungkinkan untuk memeriksa apakah garpu bebas, nilai == - 1, dan mengaturnya ke pemilik garpu jika gratis, dalam satu operasi atom. Lihat kode di bawah ini.

AtomicInteger fork0 = neededForks[0];//neededForks is an array that holds the forks needed per Philosopher
AtomicInteger fork1 = neededForks[1];
while(true){    
    if (Hungry) {
        //if fork is free (==-1) then grab it by denoting who took it
        if (!fork0.compareAndSet(-1, p) || !fork1.compareAndSet(-1, p)) {
          //at least one fork was not succesfully grabbed, release both and try again later
            fork0.compareAndSet(p, -1);
            fork1.compareAndSet(p, -1);
            try {
                synchronized (lock) {//sleep and get notified later when a philosopher puts down one fork                    
                    lock.wait();//try again later, goes back up the loop
                }
            } catch (InterruptedException e) {}

        } else {
            //sucessfully grabbed both forks
            transition(fork_l_free_and_fork_r_free);
        }
    }
}

Karena metode compareAndSet tidak memblokir, itu harus meningkatkan throughput, lebih banyak pekerjaan yang dilakukan. Seperti yang Anda ketahui, masalah Makan Filsuf digunakan ketika terkontrol diakses ke sumber daya diperlukan, yaitu garpu, diperlukan, seperti proses membutuhkan sumber daya untuk terus melakukan pekerjaan.

Rodrigo Gomez
sumber
0

Contoh sederhana untuk fungsi compareAndSet ():

import java.util.concurrent.atomic.AtomicInteger; 

public class GFG { 
    public static void main(String args[]) 
    { 

        // Initially value as 0 
        AtomicInteger val = new AtomicInteger(0); 

        // Prints the updated value 
        System.out.println("Previous value: "
                           + val); 

        // Checks if previous value was 0 
        // and then updates it 
        boolean res = val.compareAndSet(0, 6); 

        // Checks if the value was updated. 
        if (res) 
            System.out.println("The value was"
                               + " updated and it is "
                           + val); 
        else
            System.out.println("The value was "
                               + "not updated"); 
      } 
  } 

Dicetak adalah: nilai sebelumnya: 0 Nilai telah diperbarui dan itu adalah 6 Contoh sederhana lainnya:

    import java.util.concurrent.atomic.AtomicInteger; 

public class GFG { 
    public static void main(String args[]) 
    { 

        // Initially value as 0 
        AtomicInteger val 
            = new AtomicInteger(0); 

        // Prints the updated value 
        System.out.println("Previous value: "
                           + val); 

         // Checks if previous value was 0 
        // and then updates it 
        boolean res = val.compareAndSet(10, 6); 

          // Checks if the value was updated. 
          if (res) 
            System.out.println("The value was"
                               + " updated and it is "
                               + val); 
        else
            System.out.println("The value was "
                               + "not updated"); 
    } 
} 

Dicetak adalah: Nilai sebelumnya: 0 Nilai tidak diperbarui

M Shafaei N
sumber