Apa perbedaan antara atom / volatil / tersinkronisasi?


297

Bagaimana cara kerja atom / volatile / tersinkronisasi secara internal?

Apa perbedaan antara blok kode berikut?

Kode 1

private int counter;

public int getNextUniqueIndex() {
    return counter++; 
}

Kode 2

private AtomicInteger counter;

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

Kode 3

private volatile int counter;

public int getNextUniqueIndex() {
    return counter++; 
}

Apakah volatilebekerja dengan cara berikut? Adalah

volatile int i = 0;
void incIBy5() {
    i += 5;
}

setara dengan

Integer i = 5;
void incIBy5() {
    int temp;
    synchronized(i) { temp = i }
    synchronized(i) { i = temp + 5 }
}

Saya pikir dua utas tidak dapat memasuki blok yang disinkronkan secara bersamaan ... apakah saya benar? Jika ini benar maka bagaimana cara atomic.incrementAndGet()kerjanya tanpa synchronized? Dan apakah ini aman?

Dan apa perbedaan antara membaca dan menulis internal dengan variabel volatil / variabel atom? Saya membaca di beberapa artikel bahwa utas memiliki salinan variabel lokal - apa itu?


5
Itu membuat banyak pertanyaan, dengan kode yang bahkan tidak bisa dikompilasi. Mungkin Anda harus membaca buku yang bagus, seperti Java Concurrency in Practice.
JB Nizet

4
@JBNizet kamu benar !!! Saya memiliki buku itu, tidak memiliki konsep Atom secara singkat dan saya tidak mendapatkan beberapa konsep tentang itu. kutukan itu kesalahan saya bukan penulis.
hardik

4
Anda tidak benar-benar harus peduli bagaimana itu diterapkan (dan itu bervariasi dengan OS) Yang harus Anda pahami adalah kontrak: nilainya bertambah secara atom, dan semua utas lainnya dijamin untuk melihat nilai baru.
JB Nizet

Jawaban:


392

Anda secara khusus bertanya tentang bagaimana mereka bekerja secara internal , jadi di sini Anda berada:

Tidak ada sinkronisasi

private int counter;

public int getNextUniqueIndex() {
  return counter++; 
}

Ini pada dasarnya membaca nilai dari memori, menambahkannya dan mengembalikannya ke memori. Ini bekerja di utas tunggal tetapi saat ini, di era multi-core, multi-CPU, multi-level cache tidak akan berfungsi dengan benar. Pertama-tama ini memperkenalkan kondisi balapan (beberapa utas dapat membaca nilai pada saat yang sama), tetapi juga masalah visibilitas. Nilai hanya dapat disimpan dalam memori CPU " lokal " (beberapa cache) dan tidak terlihat untuk CPU / core lainnya (dan karenanya - utas). Inilah sebabnya mengapa banyak merujuk pada salinan variabel lokal di suatu utas. Sangat tidak aman. Pertimbangkan kode penghentian utas populer ini:

private boolean stopped;

public void run() {
    while(!stopped) {
        //do some work
    }
}

public void pleaseStop() {
    stopped = true;
}

Tambahkan volatileke stoppedvariabel dan berfungsi dengan baik - jika ada utas lain yang memodifikasi stoppedvariabel melalui pleaseStop()metode, Anda dijamin akan segera melihat perubahan itu dalam while(!stopped)loop utas yang berfungsi . BTW ini juga bukan cara yang baik untuk menghentikan utas, lihat: Cara menghentikan utas yang berjalan selamanya tanpa menggunakan dan Menghentikan utas java tertentu .

AtomicInteger

private AtomicInteger counter = new AtomicInteger();

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

The AtomicIntegerpenggunaan kelas CAS ( membandingkan-dan-swap ) operasi tingkat rendah CPU (tidak ada sinkronisasi diperlukan!) Mereka memungkinkan Anda untuk memodifikasi variabel tertentu hanya jika nilai sekarang sama dengan sesuatu yang lain (dan dikembalikan berhasil). Jadi, ketika Anda menjalankannya getAndIncrement(), sebenarnya berjalan dalam satu lingkaran (implementasi nyata yang disederhanakan):

int current;
do {
  current = get();
} while(!compareAndSet(current, current + 1));

Jadi pada dasarnya: baca; mencoba menyimpan nilai yang bertambah; jika tidak berhasil (nilainya tidak lagi sama dengan current), baca dan coba lagi. The compareAndSet()diimplementasikan dalam kode asli (assembly).

volatile tanpa sinkronisasi

private volatile int counter;

public int getNextUniqueIndex() {
  return counter++; 
}

Kode ini tidak benar. Ini memperbaiki masalah visibilitas ( volatilememastikan utas lain dapat melihat perubahan dibuat pada counter) tetapi masih memiliki kondisi balapan. Ini telah dijelaskan berulang kali: pra / pasca penambahan bukan atom.

Satu-satunya efek samping dari cache volatileadalah " flushing " sehingga semua pihak lain melihat versi terbaru dari data tersebut. Ini terlalu ketat dalam kebanyakan situasi; itu sebabnya volatiletidak standar.

volatile tanpa sinkronisasi (2)

volatile int i = 0;
void incIBy5() {
  i += 5;
}

Masalah yang sama seperti di atas, tetapi lebih buruk karena itidak private. Kondisi balapan masih ada. Mengapa ini menjadi masalah? Jika, katakanlah, dua utas menjalankan kode ini secara bersamaan, hasilnya mungkin + 5atau + 10. Namun, Anda dijamin melihat perubahannya.

Independen berganda synchronized

void incIBy5() {
  int temp;
  synchronized(i) { temp = i }
  synchronized(i) { i = temp + 5 }
}

Kejutan, kode ini juga salah. Faktanya, itu sepenuhnya salah. Pertama-tama Anda menyinkronkan i, yang akan diubah (apalagi, iadalah primitif, jadi saya kira Anda menyinkronkan pada sementara yang Integerdibuat melalui autoboxing ...) Benar-benar cacat. Anda juga bisa menulis:

synchronized(new Object()) {
  //thread-safe, SRSLy?
}

Tidak ada dua utas yang dapat memasuki synchronizedblok yang sama dengan kunci yang sama . Dalam hal ini (dan juga dalam kode Anda) objek kunci berubah pada setiap eksekusi, jadisynchronized secara efektif tidak berpengaruh.

Bahkan jika Anda telah menggunakan variabel akhir (atau this) untuk sinkronisasi, kode tersebut masih salah. Dua thread pertama dapat membaca iuntuk tempserempak (memiliki nilai yang sama secara lokal di temp), maka pihak yang ditunjuk pertama nilai baru untuk i(katakanlah, dari 1 sampai 6) dan yang lain melakukan hal yang sama (1-6).

Sinkronisasi harus berkisar dari membaca hingga menetapkan nilai. Sinkronisasi pertama Anda tidak memiliki efek (membaca intatom adalah) dan yang kedua juga. Menurut pendapat saya, ini adalah bentuk yang benar:

void synchronized incIBy5() {
  i += 5 
}

void incIBy5() {
  synchronized(this) {
    i += 5 
  }
}

void incIBy5() {
  synchronized(this) {
    int temp = i;
    i = temp + 5;
  }
}

10
Satu-satunya hal yang saya tambahkan adalah bahwa JVM menyalin nilai-nilai variabel ke dalam register untuk beroperasi pada mereka. Ini berarti utas yang berjalan pada satu CPU / core masih dapat melihat nilai yang berbeda untuk variabel yang tidak mudah menguap.
David Harkness

@ Thomasz: Apakah disamakanAndSet (saat ini, saat ini + 1) disinkronkan ?? jika tidak daripada apa yang terjadi ketika dua utas mengeksekusi metode ini pada saat yang sama ??
hardik

@ Hardik: compareAndSethanya pembungkus tipis di sekitar operasi CAS. Saya masuk ke beberapa detail dalam jawaban saya.
Tomasz Nurkiewicz

1
@ thomsasz: ok, saya melihat pertanyaan tautan ini dan dijawab oleh jon skeet, katanya, "utas tidak dapat membaca variabel yang tidak stabil tanpa memeriksa apakah ada utas lain yang melakukan penulisan." tetapi apa yang terjadi jika ada satu utas di antara operasi penulisan dan utas kedua adalah membacanya !! Apakah aku salah ?? bukankah kondisi balapan pada operasi atom ??
hardik

3
@Hardik: tolong buat pertanyaan lain untuk mendapatkan lebih banyak tanggapan atas apa yang Anda tanyakan, di sini hanya Anda dan saya dan komentar tidak sesuai untuk mengajukan pertanyaan. Jangan lupa untuk memposting tautan ke pertanyaan baru di sini sehingga saya dapat menindaklanjutinya.
Tomasz Nurkiewicz

61

Mendeklarasikan suatu variabel sebagai volatile berarti mengubah nilainya dengan segera memengaruhi penyimpanan memori aktual untuk variabel tersebut. Kompiler tidak dapat mengoptimalkan semua referensi yang dibuat untuk variabel. Ini menjamin bahwa ketika satu utas memodifikasi variabel, semua utas lainnya segera melihat nilai baru. (Ini tidak dijamin untuk variabel non-volatil.)

Mendeklarasikan variabel atom menjamin bahwa operasi yang dilakukan pada variabel terjadi dengan cara atom, yaitu, bahwa semua subteps operasi diselesaikan dalam utas yang dieksekusi dan tidak terganggu oleh utas lainnya. Sebagai contoh, operasi peningkatan-dan-pengujian membutuhkan variabel yang akan bertambah dan kemudian dibandingkan dengan nilai lain; operasi atom menjamin bahwa kedua langkah ini akan diselesaikan seolah-olah itu adalah operasi tunggal yang tidak terpisahkan / tidak terputus.

Menyinkronkan semua akses ke suatu variabel memungkinkan hanya satu utas pada satu waktu untuk mengakses variabel, dan memaksa semua utas lainnya untuk menunggu agar utas yang mengakses melepaskan aksesnya ke variabel.

Akses yang disinkronkan mirip dengan akses atom, tetapi operasi atom umumnya diimplementasikan pada level pemrograman yang lebih rendah. Juga, sangat mungkin untuk menyinkronkan hanya beberapa akses ke suatu variabel dan memungkinkan akses lain untuk tidak disinkronkan (misalnya, menyinkronkan semua penulisan ke variabel tetapi tidak ada yang membaca dari itu).

Atomicity, sinkronisasi, dan volatilitas adalah atribut independen, tetapi biasanya digunakan dalam kombinasi untuk menegakkan kerjasama thread yang tepat untuk mengakses variabel.

Addendum (April 2016)

Akses tersinkronisasi ke variabel biasanya diimplementasikan menggunakan monitor atau semaphore . Ini adalah mekanisme mutex (saling pengecualian) tingkat rendah yang memungkinkan utas untuk mendapatkan kendali atas variabel atau blok kode secara eksklusif, memaksa semua utas lainnya menunggu jika mereka juga berupaya untuk mendapatkan muteks yang sama. Setelah utas memiliki melepaskan mutex, utas lain dapat memperoleh mutex pada gilirannya.

Addendum (Juli 2016)

Sinkronisasi terjadi pada suatu objek . Ini berarti bahwa memanggil metode yang disinkronkan kelas akan mengunci thisobjek panggilan. Metode tersinkronisasi statis akan mengunci Classobjek itu sendiri.

Demikian juga, memasuki blok yang disinkronkan memerlukan penguncian thisobjek metode.

Ini berarti bahwa metode yang disinkronkan (atau blok) dapat mengeksekusi di beberapa utas pada saat yang sama jika mereka mengunci pada objek yang berbeda , tetapi hanya satu utas yang dapat menjalankan metode yang disinkronkan (atau memblokir) pada suatu waktu untuk objek tunggal tertentu .


25

lincah:

volatileadalah kata kunci. volatilememaksa semua utas untuk mendapatkan nilai terbaru dari variabel dari memori utama alih-alih dari cache. Tidak diperlukan penguncian untuk mengakses variabel volatil. Semua utas dapat mengakses nilai variabel volatil pada saat yang sama.

Menggunakan volatilevariabel mengurangi risiko kesalahan konsistensi memori, karena setiap penulisan ke variabel volatil membentuk hubungan yang terjadi sebelum dengan membaca selanjutnya dari variabel yang sama.

Ini berarti bahwa perubahan pada suatu volatilevariabel selalu terlihat oleh utas lainnya . Terlebih lagi, ini juga berarti bahwa ketika utas membaca volatilevariabel, ia tidak hanya melihat perubahan terbaru ke volatil, tetapi juga efek samping dari kode yang menyebabkan perubahan .

Kapan harus digunakan: Satu utas mengubah data dan utas lainnya harus membaca nilai data terbaru. Utas lain akan mengambil tindakan tetapi tidak memperbarui data .

AtomicXXX:

AtomicXXXkelas mendukung pemrograman thread-safe bebas kunci pada variabel tunggal. AtomicXXXKelas - kelas ini (seperti AtomicInteger) menyelesaikan kesalahan inkonsistensi memori / efek samping dari modifikasi variabel volatil, yang telah diakses di banyak utas.

Kapan harus menggunakan: Beberapa utas dapat membaca dan mengubah data.

disinkronkan:

synchronizedadalah kata kunci yang digunakan untuk menjaga metode atau blok kode. Dengan membuat metode yang disinkronkan memiliki dua efek:

  1. Pertama, tidak mungkin untuk dua pemanggilan synchronizedmetode pada objek yang sama untuk interleave. Ketika satu utas mengeksekusi synchronizedmetode untuk objek, semua utas lain yang memanggil synchronizedmetode untuk blok objek yang sama (menunda eksekusi) hingga utas pertama selesai dengan objek.

  2. Kedua, ketika suatu synchronizedmetode keluar, itu secara otomatis membangun hubungan yang terjadi sebelum dengan setiap permohonan synchronizedmetode untuk objek yang sama. Ini menjamin bahwa perubahan keadaan objek terlihat oleh semua utas.

Kapan harus menggunakan: Beberapa utas dapat membaca dan mengubah data. Logika bisnis Anda tidak hanya memperbarui data tetapi juga menjalankan operasi atom

AtomicXXXsetara volatile + synchronizedmeskipun implementasinya berbeda. AmtomicXXXmemperluas volatilevariabel + compareAndSetmetode tetapi tidak menggunakan sinkronisasi.

Pertanyaan SE terkait:

Perbedaan antara volatile dan disinkronkan di Jawa

Volatile boolean vs AtomicBoolean

Artikel bagus untuk dibaca: (Konten di atas diambil dari halaman dokumentasi ini)

https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html

https://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html


2
Ini adalah jawaban pertama yang benar-benar menyebutkan semantik yang terjadi sebelum kata kunci / fitur yang dijelaskan, yang penting dalam memahami bagaimana mereka sebenarnya mempengaruhi eksekusi kode. Jawaban dengan suara lebih tinggi tidak melihat aspek ini.
jhyot

5

Saya tahu bahwa dua utas tidak dapat masuk di blok Sinkronkan secara bersamaan

Dua utas tidak dapat memasuki blok yang disinkronkan pada objek yang sama dua kali. Ini berarti bahwa dua utas dapat memasuki blok yang sama pada objek yang berbeda. Kebingungan ini dapat menyebabkan kode seperti ini.

private Integer i = 0;

synchronized(i) {
   i++;
}

Ini tidak akan berperilaku seperti yang diharapkan karena bisa mengunci objek yang berbeda setiap kali.

jika ini benar daripada Bagaimana atomic.incrementAndGet () ini bekerja tanpa Sinkronisasi ?? dan apakah thread aman ??

Iya. Itu tidak menggunakan penguncian untuk mencapai keamanan benang.

Jika Anda ingin tahu cara kerjanya lebih detail, Anda dapat membaca kode untuk mereka.

Dan apa perbedaan antara membaca internal dan menulis ke Volatile Variable / Atomic Variable ??

Kelas atom menggunakan bidang yang mudah menguap . Tidak ada perbedaan di lapangan. Perbedaannya adalah operasi yang dilakukan. Kelas Atom menggunakan operasi CompareAndSwap atau CAS.

saya membaca di beberapa artikel bahwa thread memiliki salinan variabel lokal apa itu ??

Saya hanya dapat berasumsi bahwa itu merujuk pada fakta bahwa setiap CPU memiliki tampilan cache memori yang berbeda dari setiap CPU lainnya. Untuk memastikan CPU Anda memiliki tampilan data yang konsisten, Anda perlu menggunakan teknik keamanan utas.

Ini hanya masalah ketika memori dibagikan setidaknya satu utas memperbaruinya.


@Aniket Thakur Anda yakin tentang itu? Integer tidak dapat diubah. Jadi i ++ mungkin akan secara otomatis membuka kotak nilai int, menambahkannya, dan kemudian membuat Integer baru, yang bukan contoh yang sama seperti sebelumnya. Cobalah membuat i final dan Anda akan mendapatkan kesalahan kompiler saat memanggil i ++.
fuemf5

2

Vs Atomic Vs Volatile yang Disinkronkan:

  • Volatile dan Atomic hanya berlaku pada variabel, Sedangkan Synchronized berlaku pada metode.
  • Volatile memastikan tentang visibilitas bukan atomisitas / konsistensi objek, sementara yang lain keduanya memastikan visibilitas dan atomisitas.
  • Variabel volatile store dalam RAM dan lebih cepat dalam akses tetapi kami tidak dapat mencapai keselamatan Thread atau sinkronisasi tanpa kata kunci disinkronkan.
  • Sinkronisasi diimplementasikan sebagai blok tersinkronisasi atau metode yang disinkronkan sementara keduanya tidak. Kami dapat mengaitkan beberapa baris kode aman dengan bantuan kata kunci yang disinkronkan sementara dengan keduanya kami tidak dapat mencapai yang sama.
  • Disinkronkan dapat mengunci objek kelas yang sama atau objek kelas yang berbeda sementara keduanya tidak bisa.

Harap perbaiki saya jika ada sesuatu yang saya lewatkan.


1

Sinkronisasi + volatile adalah solusi bukti bodoh untuk operasi (pernyataan) menjadi atom sepenuhnya yang mencakup banyak instruksi ke CPU.

Katakan misalnya: volatile int i = 2; i ++, yang tidak lain adalah i = i +1; yang menjadikan saya sebagai nilai 3 di memori setelah eksekusi pernyataan ini. Ini termasuk membaca nilai yang ada dari memori untuk i (yaitu 2), memuat ke register akumulator CPU dan lakukan dengan perhitungan dengan menambah nilai yang ada dengan satu (2 + 1 = 3 dalam akumulator) dan kemudian menulis kembali nilai yang bertambah itu kembali ke memori. Operasi ini tidak cukup atom meskipun nilainya volatile. i menjadi volatile hanya menjamin bahwa TUNGGAL baca / tulis dari memori adalah atom dan tidak dengan GANDA. Oleh karena itu, kita perlu melakukan sinkronisasi juga di sekitar i ++ agar tetap menjadi pernyataan atom yang bodoh. Ingat fakta bahwa suatu pernyataan mencakup banyak pernyataan.

Semoga penjelasannya cukup jelas.


1

Java volatile modifier adalah contoh mekanisme khusus untuk menjamin bahwa komunikasi terjadi di antara utas. Ketika satu utas menulis ke variabel volatil, dan utas lainnya melihat tulis itu, utas pertama memberi tahu yang kedua tentang semua isi memori hingga ia melakukan penulisan pada variabel yang tidak stabil tersebut.

Operasi atom dilakukan dalam satu unit tugas tanpa gangguan dari operasi lain. Operasi atom adalah keharusan dalam lingkungan multi-utas untuk menghindari inkonsistensi data.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.