Peringatan muncul setiap kali saya menyinkronkan pada bidang kelas non-final. Ini kodenya:
public class X
{
private Object o;
public void setO(Object o)
{
this.o = o;
}
public void x()
{
synchronized (o) // synchronization on a non-final field
{
}
}
}
jadi saya mengubah pengkodean dengan cara berikut:
public class X
{
private final Object o;
public X()
{
o = new Object();
}
public void x()
{
synchronized (o)
{
}
}
}
Saya tidak yakin kode di atas adalah cara yang tepat untuk melakukan sinkronisasi pada bidang kelas non-final. Bagaimana cara menyinkronkan bidang non final?
o
dirujuk pada saat blok yang disinkronkan tercapai. Jika objek yango
merujuk ke perubahan, thread lain dapat datang dan menjalankan blok kode tersinkronisasi.this
- Saya akan merekomendasikan membuat variabel terakhir di kelas hanya untuk tujuan penguncian , yang menghentikan orang lain untuk mengunci objek yang sama.Ini benar-benar bukan ide yang bagus - karena blok tersinkronisasi Anda tidak lagi benar-benar disinkronkan secara konsisten.
Dengan asumsi blok tersinkronisasi dimaksudkan untuk memastikan bahwa hanya satu utas yang mengakses beberapa data bersama pada satu waktu, pertimbangkan:
Mengapa Anda ingin ini terjadi? Mungkin ada beberapa situasi yang sangat terspesialisasi yang masuk akal ... tetapi Anda harus memberi saya kasus penggunaan tertentu (bersama dengan cara-cara mengurangi jenis skenario yang saya berikan di atas) sebelum saya puas dengan Itu.
sumber
o
) - dan di tengah jalan eksekusi mulai memutasi daftar yang berbeda. Bagaimana itu bisa menjadi ide yang bagus? Saya pikir pada dasarnya kami tidak setuju apakah mengunci benda yang Anda sentuh dengan cara lain atau tidak. Saya lebih suka bisa bernalar tentang kode saya tanpa pengetahuan tentang apa yang dilakukan kode lain dalam hal penguncian.Saya setuju dengan salah satu komentar John: Anda harus selalu menggunakan dummy kunci terakhir saat mengakses variabel non-final untuk mencegah ketidakkonsistenan jika referensi variabel berubah. Jadi dalam kasus apa pun dan sebagai aturan praktis pertama:
Aturan # 1: Jika sebuah field bukan final, selalu gunakan dummy kunci final (private).
Alasan # 1: Anda memegang kunci dan mengubah referensi variabel sendiri. Utas lain yang menunggu di luar kunci tersinkronisasi akan dapat memasuki blok yang dijaga.
Alasan # 2: Anda memegang kunci dan utas lain mengubah referensi variabel. Hasilnya sama: utas lain dapat memasuki blok yang dilindungi.
Tetapi ketika menggunakan dummy kunci terakhir, ada masalah lain : Anda mungkin mendapatkan data yang salah, karena objek non-final Anda hanya akan disinkronkan dengan RAM saat memanggil sinkronisasi (objek). Jadi, sebagai aturan praktis kedua:
Aturan # 2: Saat mengunci objek non-final, Anda selalu perlu melakukan keduanya: Menggunakan dummy kunci terakhir dan kunci objek non-final demi sinkronisasi RAM. (Satu-satunya alternatif adalah mendeklarasikan semua bidang objek sebagai volatile!)
Kunci ini juga disebut "kunci bersarang". Perhatikan bahwa Anda harus memanggil mereka selalu dalam urutan yang sama, jika tidak, Anda akan menemui jalan buntu :
public class X { private final LOCK; private Object o; public void setO(Object o){ this.o = o; } public void x() { synchronized (LOCK) { synchronized(o){ //do something with o... } } } }
Seperti yang Anda lihat, saya menulis kedua gembok itu langsung pada baris yang sama, karena keduanya selalu saling terkait. Seperti ini, Anda bahkan dapat melakukan 10 kunci bersarang:
synchronized (LOCK1) { synchronized (LOCK2) { synchronized (LOCK3) { synchronized (LOCK4) { //entering the locked space } } } }
Perhatikan bahwa kode ini tidak akan rusak jika Anda hanya mendapatkan kunci dalam seperti
synchronized (LOCK3)
pada utas lainnya. Tetapi itu akan rusak jika Anda memanggil di utas lain sesuatu seperti ini:synchronized (LOCK4) { synchronized (LOCK1) { //dead lock! synchronized (LOCK3) { synchronized (LOCK2) { //will never enter here... } } } }
Hanya ada satu solusi untuk mengatasi kunci bersarang tersebut saat menangani bidang non-final:
Aturan # 2 - Alternatif: Deklarasikan semua bidang objek sebagai volatile. (Saya tidak akan berbicara di sini tentang kerugian melakukan ini, misalnya mencegah penyimpanan apa pun di cache tingkat x bahkan untuk pembacaan, aso.)
Jadi oleh karena itu aioobe cukup tepat: Gunakan saja java.util.concurrent. Atau mulailah memahami segala sesuatu tentang sinkronisasi dan lakukan sendiri dengan kunci bersarang. ;)
Untuk detail lebih lanjut mengapa sinkronisasi pada bidang non-final rusak, lihat kasus pengujian saya: https://stackoverflow.com/a/21460055/2012947
Dan untuk lebih jelasnya mengapa Anda perlu disinkronkan sama sekali karena RAM dan cache, lihat di sini: https://stackoverflow.com/a/21409975/2012947
sumber
o
dengan tersinkronisasi (KUNCI) untuk membangun hubungan "terjadi-sebelum" antara setelan dan objek bacaano
. Saya membahas ini dalam pertanyaan serupa saya: stackoverflow.com/questions/32852464/…Saya tidak melihat jawaban yang benar di sini, yaitu, Tidak masalah melakukannya.
Saya bahkan tidak yakin mengapa itu peringatan, tidak ada yang salah dengan itu. JVM memastikan bahwa Anda mendapatkan kembali beberapa objek yang valid (atau null) saat Anda membaca nilai, dan Anda dapat menyinkronkan pada objek apa pun .
Jika Anda berencana untuk benar-benar mengubah kunci saat sedang digunakan (sebagai kebalikan dari misalnya mengubahnya dari metode init, sebelum Anda mulai menggunakannya), Anda harus membuat variabel yang Anda rencanakan untuk diubah
volatile
. Maka semua yang perlu Anda lakukan adalah untuk melakukan sinkronisasi pada kedua lama dan objek baru, dan Anda dapat dengan aman mengubah nilaipublic volatile Object lock;
...
synchronized (lock) { synchronized (newObject) { lock = newObject; } }
Sana. Tidak rumit, menulis kode dengan kunci (mutex) sebenarnya cukup mudah. Menulis kode tanpa mereka (kode bebas kunci) adalah hal yang sulit.
sumber
EDIT: Jadi solusi ini (seperti yang disarankan oleh Jon Skeet) mungkin memiliki masalah dengan atomicity implementasi "disinkronkan (objek) {}" sementara referensi objek berubah. Saya bertanya secara terpisah dan menurut Tuan erickson itu bukan utas aman - lihat: Apakah memasuki atom blok tersinkronisasi? . Jadi ambillah sebagai contoh bagaimana TIDAK melakukannya - dengan tautan mengapa;)
Lihat kode cara kerjanya jika synchronized () akan menjadi atom:
public class Main { static class Config{ char a='0'; char b='0'; public void log(){ synchronized(this){ System.out.println(""+a+","+b); } } } static Config cfg = new Config(); static class Doer extends Thread { char id; Doer(char id) { this.id = id; } public void mySleep(long ms){ try{Thread.sleep(ms);}catch(Exception ex){ex.printStackTrace();} } public void run() { System.out.println("Doer "+id+" beg"); if(id == 'X'){ synchronized (cfg){ cfg.a=id; mySleep(1000); // do not forget to put synchronize(cfg) over setting new cfg - otherwise following will happend // here it would be modifying different cfg (cos Y will change it). // Another problem would be that new cfg would be in parallel modified by Z cos synchronized is applied on new object cfg.b=id; } } if(id == 'Y'){ mySleep(333); synchronized(cfg) // comment this and you will see inconsistency in log - if you keep it I think all is ok { cfg = new Config(); // introduce new configuration // be aware - don't expect here to be synchronized on new cfg! // Z might already get a lock } } if(id == 'Z'){ mySleep(666); synchronized (cfg){ cfg.a=id; mySleep(100); cfg.b=id; } } System.out.println("Doer "+id+" end"); cfg.log(); } } public static void main(String[] args) throws InterruptedException { Doer X = new Doer('X'); Doer Y = new Doer('Y'); Doer Z = new Doer('Z'); X.start(); Y.start(); Z.start(); } }
sumber
AtomicReference sesuai dengan kebutuhan Anda.
Dari dokumentasi java tentang paket atom :
boolean compareAndSet(expectedValue, updateValue);
Kode sampel:
String initialReference = "value 1"; AtomicReference<String> someRef = new AtomicReference<String>(initialReference); String newReference = "value 2"; boolean exchanged = someRef.compareAndSet(initialReference, newReference); System.out.println("exchanged: " + exchanged);
Dalam contoh di atas, Anda mengganti
String
dengan milik Anda sendiriObject
Pertanyaan SE terkait:
Kapan menggunakan AtomicReference di Java?
sumber
Jika
o
tidak pernah berubah selama masa pakai instanceX
, versi kedua adalah gaya yang lebih baik terlepas dari apakah sinkronisasi terlibat.Sekarang, apakah ada yang salah dengan versi pertama tidak mungkin dijawab tanpa mengetahui apa lagi yang terjadi di kelas itu. Saya akan cenderung setuju dengan kompiler bahwa itu memang terlihat rawan kesalahan (saya tidak akan mengulangi apa yang dikatakan orang lain).
sumber
Hanya menambahkan dua sen saya: Saya mendapat peringatan ini ketika saya menggunakan komponen yang dipakai melalui desainer, jadi bidangnya tidak dapat benar-benar final, karena konstruktor tidak dapat mengambil parameter. Dengan kata lain, saya memiliki bidang kuasi-final tanpa kata kunci akhir.
Saya pikir itu sebabnya ini hanya peringatan: Anda mungkin melakukan sesuatu yang salah, tetapi mungkin juga benar.
sumber