Mengapa i++
tidak atom di Jawa?
Untuk lebih memahami Java, saya mencoba menghitung seberapa sering loop di thread dijalankan.
Jadi saya menggunakan file
private static int total = 0;
di kelas utama.
Saya memiliki dua utas.
- Thread 1: Cetakan
System.out.println("Hello from Thread 1!");
- Benang 2: Cetakan
System.out.println("Hello from Thread 2!");
Dan saya menghitung garis yang dicetak oleh utas 1 dan utas 2. Tetapi garis utas 1 + garis utas 2 tidak cocok dengan jumlah total garis yang dicetak.
Ini kode saya:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
public class Test {
private static int total = 0;
private static int countT1 = 0;
private static int countT2 = 0;
private boolean run = true;
public Test() {
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
newCachedThreadPool.execute(t1);
newCachedThreadPool.execute(t2);
try {
Thread.sleep(1000);
}
catch (InterruptedException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
}
run = false;
try {
Thread.sleep(1000);
}
catch (InterruptedException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
}
System.out.println((countT1 + countT2 + " == " + total));
}
private Runnable t1 = new Runnable() {
@Override
public void run() {
while (run) {
total++;
countT1++;
System.out.println("Hello #" + countT1 + " from Thread 2! Total hello: " + total);
}
}
};
private Runnable t2 = new Runnable() {
@Override
public void run() {
while (run) {
total++;
countT2++;
System.out.println("Hello #" + countT2 + " from Thread 2! Total hello: " + total);
}
}
};
public static void main(String[] args) {
new Test();
}
}
java
multithreading
concurrency
Andie2302
sumber
sumber
AtomicInteger
?iinc
operasi untuk menambah bilangan bulat, tetapi itu hanya berfungsi untuk variabel lokal, di mana konkurensi tidak menjadi perhatian. Untuk bidang, kompilator membuat perintah baca-ubah-tulis secara terpisah.iinc
instruksi untuk bidang, memiliki instruksi tunggal tidak menjamin atomisitas, misalnya akses non-volatile
long
dandouble
bidang tidak dijamin menjadi atom terlepas dari fakta bahwa itu dilakukan oleh instruksi bytecode tunggal.Jawaban:
i++
mungkin tidak bersifat atomik di Jawa karena atomisitas merupakan persyaratan khusus yang tidak terdapat dalam sebagian besar penggunaani++
. Persyaratan tersebut memiliki overhead yang signifikan: ada biaya yang besar dalam membuat operasi selisih atom; ini melibatkan sinkronisasi pada tingkat perangkat lunak dan perangkat keras yang tidak perlu dilakukan secara bertahap.Anda bisa membuat argumen yang
i++
seharusnya dirancang dan didokumentasikan secara spesifik melakukan kenaikan atom, sehingga kenaikan non-atomik dilakukan dengan menggunakani = i + 1
. Namun, ini akan merusak "kompatibilitas budaya" antara Java, dan C dan C ++. Selain itu, ini akan menghilangkan notasi yang nyaman yang dianggap biasa oleh programmer yang akrab dengan bahasa C-like, memberinya makna khusus yang hanya berlaku dalam keadaan terbatas.Kode dasar C atau C ++
for (i = 0; i < LIMIT; i++)
akan diterjemahkan ke dalam Java sebagaifor (i = 0; i < LIMIT; i = i + 1)
; karena tidak pantas menggunakan atomi++
. Yang lebih buruk, pemrogram yang berasal dari C atau bahasa mirip C lainnya ke Java akani++
tetap menggunakannya , mengakibatkan penggunaan instruksi atomik yang tidak perlu.Bahkan pada level set instruksi mesin, operasi tipe increment biasanya tidak atomic karena alasan kinerja. Dalam x86, instruksi khusus "kunci awalan" harus digunakan untuk membuat
inc
instruksi atom: untuk alasan yang sama seperti di atas. Jikainc
selalu atom, itu tidak akan pernah digunakan ketika inc non-atom diperlukan; programmer dan compiler akan menghasilkan kode yang memuat, menambahkan 1 dan menyimpan, karena itu akan jauh lebih cepat.Dalam beberapa arsitektur set instruksi, tidak ada atom
inc
atau mungkin tidak adainc
sama sekali; untuk melakukan atomic inc di MIPS, Anda harus menulis loop perangkat lunak yang menggunakanll
andsc
: load-linked, dan store-conditional. Load-linked membaca kata tersebut, dan store-conditional menyimpan nilai baru jika kata tersebut tidak berubah, atau gagal (yang terdeteksi dan menyebabkan percobaan ulang).sumber
i = i + 1
akan menjadi terjemahan untuk++i
, bukani++
volatile
bidang. Jadi, kecuali Anda akan memperlakukan setiap bidang secara implisitvolatile
setelah satu utas menggunakan++
operator di atasnya, jaminan atomisitas seperti itu tidak akan menyelesaikan masalah pembaruan serentak. Jadi mengapa berpotensi membuang-buang kinerja untuk sesuatu jika itu tidak menyelesaikan masalah.++
? ;)i++
melibatkan dua operasi:i
i
Saat dua utas bekerja
i++
pada variabel yang sama pada saat yang sama, keduanya mungkin mendapatkan nilai saat ini yang samai
, lalu menaikkan dan menyetelnya kei+1
, jadi Anda akan mendapatkan satu inkrementasi, bukan dua.Contoh:
sumber
i++
bersifat atom, itu tidak akan didefinisikan dengan baik / perilaku aman utas.)Yang penting adalah JLS (Spesifikasi Bahasa Java) daripada bagaimana berbagai implementasi JVM mungkin atau mungkin tidak mengimplementasikan fitur bahasa tertentu. JLS mendefinisikan operator ++ postfix dalam klausul 15.14.2 yang mengatakan ia "nilai 1 ditambahkan ke nilai variabel dan jumlahnya disimpan kembali ke dalam variabel". Tidak ada yang menyebutkan atau mengisyaratkan multithreading atau atomicity. Untuk ini, JLS menyediakan volatile dan sinkronisasi . Selain itu, ada paket java.util.concurrent.atomic (lihat http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/atomic/package-summary.html )
sumber
Mari kita pecahkan operasi increment menjadi beberapa pernyataan:
Benang 1 & 2:
Jika tidak ada sinkronisasi, katakanlah Thread satu telah membaca nilai 3 dan menaikkannya menjadi 4, tetapi belum menuliskannya kembali. Pada titik ini, pengalih konteks terjadi. Utas dua membaca nilai 3, menambahnya dan sakelar konteks terjadi. Meskipun kedua utas telah menambah nilai total, itu masih akan menjadi 4 - kondisi balapan.
sumber
i++
adalah pernyataan yang hanya melibatkan 3 operasi:Ketiga operasi ini tidak dimaksudkan untuk dijalankan dalam satu langkah atau dengan kata lain
i++
bukan operasi gabungan . Akibatnya, segala macam hal bisa salah jika lebih dari satu utas terlibat dalam operasi tunggal tetapi non-gabungan.Pertimbangkan skenario berikut ini:
Waktu 1 :
Waktu 2 :
Waktu 3 :
Dan begitulah. Kondisi balapan.
Itu sebabnya
i++
tidak atom. Jika ya, semua ini tidak akan terjadi dan masing-masingfetch-update-store
akan terjadi secara atomik. Itulah tepatnyaAtomicInteger
untuk dan dalam kasus Anda itu mungkin cocok.PS
Sebuah buku yang sangat bagus yang mencakup semua masalah itu dan beberapa di antaranya adalah ini: Java Concurrency in Practice
sumber
i++
tidak atomik.Dalam JVM, kenaikan melibatkan membaca dan menulis, jadi ini bukan atom.
sumber
Jika operasi ini
i++
akan menjadi atom Anda tidak akan memiliki kesempatan untuk membaca nilai darinya. Inilah yang ingin Anda lakukan dengan menggunakani++
(daripada menggunakan++i
).Misalnya lihat kode berikut:
Dalam hal ini kami mengharapkan outputnya menjadi:
0
(karena kami memposting kenaikan, misalnya membaca pertama, kemudian memperbarui)Ini adalah salah satu alasan mengapa operasi tidak bisa atomic, karena Anda perlu membaca nilai (dan melakukan sesuatu dengannya) dan kemudian memperbarui nilainya.
Alasan penting lainnya adalah melakukan sesuatu secara atomik biasanya membutuhkan lebih banyak waktu karena penguncian. Akan konyol jika semua operasi pada primitif membutuhkan waktu sedikit lebih lama untuk kasus yang jarang terjadi ketika orang ingin melakukan operasi atom. Itulah mengapa mereka telah menambahkan
AtomicInteger
dan kelas atom lainnya ke bahasa tersebut.sumber
i++
untuk diperluas kei.getAndIncrement()
. Perluasan seperti itu bukanlah hal baru. Misalnya, lambda di C ++ diperluas ke definisi kelas anonim di C ++.i++
seseorang dapat dengan mudah membuat atom++i
atau sebaliknya. Yang satu setara dengan yang lainnya plus satu.Ada dua langkah:
jadi ini bukan operasi atom. Ketika thread1 menjalankan i ++, dan thread2 menjalankan i ++, nilai akhir dari i mungkin adalah i + 1.
sumber
Concurrency (
Thread
kelas dan semacamnya) adalah fitur tambahan di v1.0 Java .i++
telah ditambahkan dalam versi beta sebelumnya, dan karena itu, kemungkinan besar masih dalam penerapan aslinya (kurang lebih).Terserah programmer untuk menyinkronkan variabel. Lihat tutorial Oracle tentang ini .
Sunting: Untuk memperjelas, i ++ adalah prosedur yang terdefinisi dengan baik yang mendahului Java, dan karena itu perancang Java memutuskan untuk mempertahankan fungsionalitas asli dari prosedur itu.
Operator ++ didefinisikan dalam B (1969) yang mendahului java dan threading hanya oleh sedikit.
sumber
i++
tidak menjadi atom adalah keputusan desain, bukan pengawasan dalam sistem yang berkembang.