Kapan kita menggunakan AtomicReference
?
Apakah diperlukan untuk membuat objek di semua program multithread?
Berikan contoh sederhana di mana AtomicReference harus digunakan.
Kapan kita menggunakan AtomicReference
?
Apakah diperlukan untuk membuat objek di semua program multithread?
Berikan contoh sederhana di mana AtomicReference harus digunakan.
Jawaban:
Referensi atom harus digunakan dalam pengaturan di mana Anda perlu melakukan operasi atom sederhana (yaitu thread-safe , non-trivial) pada referensi, di mana sinkronisasi berbasis monitor tidak tepat. Misalkan Anda ingin memeriksa untuk melihat apakah bidang tertentu hanya jika keadaan objek tetap saat Anda terakhir memeriksa:
AtomicReference<Object> cache = new AtomicReference<Object>();
Object cachedValue = new Object();
cache.set(cachedValue);
//... time passes ...
Object cachedValueToUpdate = cache.get();
//... do some work to transform cachedValueToUpdate into a new version
Object newValue = someFunctionOfOld(cachedValueToUpdate);
boolean success = cache.compareAndSet(cachedValue,cachedValueToUpdate);
Karena semantik referensi atom, Anda dapat melakukan ini bahkan jika cache
objek dibagi di antara utas, tanpa menggunakan synchronized
. Secara umum, Anda lebih baik menggunakan sinkronisasi atau java.util.concurrent
kerangka daripada telanjang Atomic*
kecuali Anda tahu apa yang Anda lakukan.
Dua referensi pohon mati yang bagus yang akan memperkenalkan Anda pada topik ini:
Perhatikan bahwa (saya tidak tahu apakah ini selalu benar) tugas referensi (yaitu =
) itu sendiri adalah atomik (memperbarui tipe 64-bit primitif suka long
atau double
mungkin tidak menjadi atom; tetapi memperbarui referensi selalu atom, meskipun itu 64 bit ) tanpa secara eksplisit menggunakan Atomic*
.
Lihat Spesifikasi Bahasa Jawa 3ed, Bagian 17.7 .
AtomicReference
Anda harus menandai variabel volatile
karena sementara runtime menjamin bahwa penugasan referensi adalah atom, kompiler dapat melakukan optimasi dengan asumsi bahwa variabel tidak sedang dimodifikasi oleh utas lainnya.
AtomicReference
"; jika Anda sedang menggunakannya, maka saran saya akan pergi ke arah yang berlawanan dan menandainya final
sehingga compiler dapat mengoptimalkannya.
Referensi atom sangat ideal untuk digunakan ketika Anda perlu berbagi dan mengubah keadaan objek abadi antara beberapa utas. Itu adalah pernyataan yang sangat padat jadi saya akan memecahnya sedikit.
Pertama, objek abadi adalah objek yang secara efektif tidak berubah setelah konstruksi. Seringkali metode objek yang tidak dapat diubah mengembalikan instance baru dari kelas yang sama. Beberapa contoh termasuk kelas pembungkus Long dan Double, serta String, hanya untuk beberapa nama. (Berdasarkan Pemrograman Concurrency pada JVM objek abadi adalah bagian penting dari concurrency modern).
Selanjutnya, mengapa AtomicReference lebih baik daripada objek yang mudah menguap untuk berbagi nilai bersama itu. Contoh kode sederhana akan menunjukkan perbedaannya.
volatile String sharedValue;
static final Object lock=new Object();
void modifyString(){
synchronized(lock){
sharedValue=sharedValue+"something to add";
}
}
Setiap kali Anda ingin memodifikasi string yang dirujuk oleh bidang volatil itu berdasarkan nilai saat ini, Anda harus terlebih dahulu mendapatkan kunci pada objek itu. Ini mencegah beberapa utas lainnya masuk saat itu dan mengubah nilai di tengah rangkaian string baru. Kemudian ketika utas Anda kembali, Anda akan merusak pekerjaan utas lainnya. Tapi jujur kode itu akan berfungsi, itu terlihat bersih, dan itu akan membuat kebanyakan orang senang.
Sedikit masalah. Itu lambat. Terutama jika ada banyak pertengkaran Obyek kunci itu. Itu karena sebagian besar kunci memerlukan panggilan sistem OS, dan utas Anda akan diblokir dan secara konteks dialihkan dari CPU untuk memberi jalan bagi proses lain.
Opsi lainnya adalah menggunakan AtomicRefrence.
public static AtomicReference<String> shared = new AtomicReference<>();
String init="Inital Value";
shared.set(init);
//now we will modify that value
boolean success=false;
while(!success){
String prevValue=shared.get();
// do all the work you need to
String newValue=shared.get()+"lets add something";
// Compare and set
success=shared.compareAndSet(prevValue,newValue);
}
Sekarang mengapa ini lebih baik? Jujur kode itu sedikit kurang bersih dari sebelumnya. Tetapi ada sesuatu yang sangat penting yang terjadi di bawah tenda di AtomicRefrence, yaitu membandingkan dan menukar. Ini adalah instruksi CPU tunggal, bukan panggilan OS, yang membuat saklar terjadi. Itu adalah instruksi tunggal pada CPU. Dan karena tidak ada kunci, tidak ada saklar konteks dalam kasus di mana kunci dijalankan yang menghemat lebih banyak waktu!
Tangkapannya adalah, untuk AtomicReferences, ini tidak menggunakan panggilan .equals (), melainkan perbandingan == untuk nilai yang diharapkan. Jadi pastikan yang diharapkan adalah objek aktual yang dikembalikan dari get in the loop.
worked
untuk mendapatkan semantik yang sama.
Berikut ini adalah kasus penggunaan untuk AtomicReference:
Pertimbangkan kelas ini yang bertindak sebagai rentang angka, dan menggunakan variabel AtmomicInteger individual untuk mempertahankan batas angka bawah dan atas.
public class NumberRange {
// INVARIANT: lower <= upper
private final AtomicInteger lower = new AtomicInteger(0);
private final AtomicInteger upper = new AtomicInteger(0);
public void setLower(int i) {
// Warning -- unsafe check-then-act
if (i > upper.get())
throw new IllegalArgumentException(
"can't set lower to " + i + " > upper");
lower.set(i);
}
public void setUpper(int i) {
// Warning -- unsafe check-then-act
if (i < lower.get())
throw new IllegalArgumentException(
"can't set upper to " + i + " < lower");
upper.set(i);
}
public boolean isInRange(int i) {
return (i >= lower.get() && i <= upper.get());
}
}
Baik setLower dan setUpper adalah urutan centang lalu tindakan, tetapi keduanya tidak menggunakan penguncian yang memadai untuk membuatnya menjadi atom. Jika rentang nomor berlaku (0, 10), dan satu utas memanggil setLower (5) sedangkan utas lainnya memanggil setUpper (4), dengan beberapa waktu sial keduanya akan melewati pemeriksaan di setter dan kedua modifikasi akan diterapkan. Hasilnya adalah bahwa rentang sekarang memegang (5, 4) keadaan tidak valid. Jadi sementara AtomicIntegers yang mendasarinya adalah thread-safe, kelas komposit tidak. Ini dapat diperbaiki dengan menggunakan AtomicReference daripada menggunakan AtomicIntegers individu untuk batas atas dan bawah.
public class CasNumberRange {
// Immutable
private static class IntPair {
final int lower; // Invariant: lower <= upper
final int upper;
private IntPair(int lower, int upper) {
this.lower = lower;
this.upper = upper;
}
}
private final AtomicReference<IntPair> values =
new AtomicReference<IntPair>(new IntPair(0, 0));
public int getLower() {
return values.get().lower;
}
public void setLower(int lower) {
while (true) {
IntPair oldv = values.get();
if (lower > oldv.upper)
throw new IllegalArgumentException(
"Can't set lower to " + lower + " > upper");
IntPair newv = new IntPair(lower, oldv.upper);
if (values.compareAndSet(oldv, newv))
return;
}
}
public int getUpper() {
return values.get().upper;
}
public void setUpper(int upper) {
while (true) {
IntPair oldv = values.get();
if (upper < oldv.lower)
throw new IllegalArgumentException(
"Can't set upper to " + upper + " < lower");
IntPair newv = new IntPair(oldv.lower, upper);
if (values.compareAndSet(oldv, newv))
return;
}
}
}
Anda dapat menggunakan AtomicReference saat menerapkan kunci optimis. Anda memiliki objek yang dibagikan dan Anda ingin mengubahnya dari lebih dari 1 utas.
Seperti utas lain yang mungkin telah memodifikasinya dan / dapat memodifikasi antara 2 langkah ini. Anda perlu melakukannya dalam operasi atom. Di sinilah AtomicReference dapat membantu
Berikut ini adalah use case yang sangat sederhana dan tidak ada hubungannya dengan keamanan thread.
Untuk berbagi objek antara permintaan lambda, AtomicReference
ini adalah opsi :
public void doSomethingUsingLambdas() {
AtomicReference<YourObject> yourObjectRef = new AtomicReference<>();
soSomethingThatTakesALambda(() -> {
yourObjectRef.set(youObject);
});
soSomethingElseThatTakesALambda(() -> {
YourObject yourObject = yourObjectRef.get();
});
}
Saya tidak mengatakan ini adalah desain yang baik atau apa pun (itu hanya contoh sepele), tetapi jika Anda memiliki kasing di mana Anda perlu berbagi objek antara permintaan lambda, AtomicReference
adalah pilihan.
Bahkan Anda dapat menggunakan objek apa pun yang memegang referensi, bahkan Koleksi yang hanya memiliki satu item. Namun, AtomicReference sangat cocok.
Saya tidak akan banyak bicara. Rekan-rekan saya yang terhormat telah memberikan masukan berharga mereka.Kode berjalan yang lengkap pada akhir blog ini akan menghilangkan kebingungan. Ini tentang program pemesanan kursi film kecil dalam skenario multi-utas.
Beberapa fakta dasar yang penting adalah sebagai berikut. 1> Berbagai utas hanya dapat bersaing sebagai contoh dan variabel anggota statis di ruang tumpukan. 2> Volatile baca atau tulis sepenuhnya atom dan serial / terjadi sebelumnya dan hanya dilakukan dari memori. Dengan mengatakan ini, saya maksudkan bahwa setiap pembacaan akan mengikuti tulisan sebelumnya dalam memori. Dan setiap tulisan akan mengikuti pembacaan sebelumnya dari memori. Jadi utas apa pun yang bekerja dengan volatil akan selalu melihat nilai paling baru. AtomicReference menggunakan properti volatil ini.
Berikut adalah beberapa kode sumber AtomicReference. AtomicReference mengacu pada referensi objek. Referensi ini adalah variabel anggota yang mudah menguap dalam contoh AtomicReference seperti di bawah ini.
private volatile V value;
dapatkan () cukup kembalikan nilai terbaru dari variabel (seperti halnya volatil dengan cara "terjadi sebelum").
public final V get()
Berikut ini adalah metode paling penting dari AtomicReference.
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
Metode compareAndSet (mengharapkan, memperbarui) memanggil metode compareAndSwapObject () dari kelas Java yang tidak aman. Metode panggilan tidak aman ini memanggil panggilan asli, yang memanggil satu instruksi ke prosesor. "harapkan" dan "perbarui" setiap referensi sebuah objek.
Jika dan hanya jika variabel anggota instance AtomicReference "nilai" merujuk ke objek yang sama disebut oleh "ekspektasi", "pembaruan" ditugaskan ke variabel instance ini sekarang, dan "benar" dikembalikan. Atau yang lain, false dikembalikan. Semuanya dilakukan secara atom. Tidak ada utas lain yang dapat mencegahnya. Karena ini adalah operasi prosesor tunggal (keajaiban arsitektur komputer modern), seringkali lebih cepat daripada menggunakan blok yang disinkronkan. Tapi ingat bahwa ketika beberapa variabel perlu diperbarui secara atom, AtomicReference tidak akan membantu.
Saya ingin menambahkan kode berjalan yang lengkap, yang dapat dijalankan di gerhana. Itu akan menghapus banyak kebingungan. Di sini 22 pengguna (utasku) mencoba memesan 20 kursi. Berikut ini adalah cuplikan kode diikuti oleh kode lengkap.
Cuplikan kode tempat 22 pengguna mencoba memesan 20 kursi.
for (int i = 0; i < 20; i++) {// 20 seats
seats.add(new AtomicReference<Integer>());
}
Thread[] ths = new Thread[22];// 22 users
for (int i = 0; i < ths.length; i++) {
ths[i] = new MyTh(seats, i);
ths[i].start();
}
Berikut ini adalah kode berjalan lengkap.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class Solution {
static List<AtomicReference<Integer>> seats;// Movie seats numbered as per
// list index
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
seats = new ArrayList<>();
for (int i = 0; i < 20; i++) {// 20 seats
seats.add(new AtomicReference<Integer>());
}
Thread[] ths = new Thread[22];// 22 users
for (int i = 0; i < ths.length; i++) {
ths[i] = new MyTh(seats, i);
ths[i].start();
}
for (Thread t : ths) {
t.join();
}
for (AtomicReference<Integer> seat : seats) {
System.out.print(" " + seat.get());
}
}
/**
* id is the id of the user
*
* @author sankbane
*
*/
static class MyTh extends Thread {// each thread is a user
static AtomicInteger full = new AtomicInteger(0);
List<AtomicReference<Integer>> l;//seats
int id;//id of the users
int seats;
public MyTh(List<AtomicReference<Integer>> list, int userId) {
l = list;
this.id = userId;
seats = list.size();
}
@Override
public void run() {
boolean reserved = false;
try {
while (!reserved && full.get() < seats) {
Thread.sleep(50);
int r = ThreadLocalRandom.current().nextInt(0, seats);// excludes
// seats
//
AtomicReference<Integer> el = l.get(r);
reserved = el.compareAndSet(null, id);// null means no user
// has reserved this
// seat
if (reserved)
full.getAndIncrement();
}
if (!reserved && full.get() == seats)
System.out.println("user " + id + " did not get a seat");
} catch (InterruptedException ie) {
// log it
}
}
}
}
Kapan kita menggunakan AtomicReference?
AtomicReference adalah cara fleksibel untuk memperbarui nilai variabel secara atomis tanpa menggunakan sinkronisasi.
AtomicReference
mendukung pemrograman thread-safe bebas kunci pada variabel tunggal.
Ada beberapa cara untuk mencapai keselamatan Thread dengan API bersamaan tingkat tinggi . Variabel atom adalah salah satu dari beberapa opsi.
Lock
objek mendukung idiom pengunci yang menyederhanakan banyak aplikasi bersamaan.
Executors
mendefinisikan API tingkat tinggi untuk meluncurkan dan mengelola utas. Implementasi pelaksana yang disediakan oleh java.util.concurrent menyediakan manajemen kumpulan thread yang cocok untuk aplikasi skala besar.
Koleksi bersamaan membuatnya lebih mudah untuk mengelola koleksi data yang besar, dan dapat sangat mengurangi kebutuhan sinkronisasi.
Variabel atom memiliki fitur yang meminimalkan sinkronisasi dan membantu menghindari kesalahan konsistensi memori.
Berikan contoh sederhana di mana AtomicReference harus digunakan.
Kode contoh dengan AtomicReference
:
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);
Apakah diperlukan untuk membuat objek di semua program multithread?
Anda tidak harus menggunakan AtomicReference
semua program multi-utas.
Jika Anda ingin menjaga satu variabel, gunakan AtomicReference
. Jika Anda ingin menjaga blok kode, gunakan konstruksi lain seperti Lock
/ synchronized
dll.
Contoh sederhana lainnya adalah melakukan modifikasi safe-thread pada objek sesi.
public PlayerScore getHighScore() {
ServletContext ctx = getServletConfig().getServletContext();
AtomicReference<PlayerScore> holder
= (AtomicReference<PlayerScore>) ctx.getAttribute("highScore");
return holder.get();
}
public void updateHighScore(PlayerScore newScore) {
ServletContext ctx = getServletConfig().getServletContext();
AtomicReference<PlayerScore> holder
= (AtomicReference<PlayerScore>) ctx.getAttribute("highScore");
while (true) {
HighScore old = holder.get();
if (old.score >= newScore.score)
break;
else if (holder.compareAndSet(old, newScore))
break;
}
}
Sumber: http://www.ibm.com/developerworks/library/j-jtp09238/index.html