เราจะใช้เมื่อไหร่AtomicReference
?
จำเป็นต้องสร้างวัตถุในโปรแกรมแบบมัลติเธรดทั้งหมดหรือไม่?
ระบุตัวอย่างง่ายๆที่ควรใช้ AtomicReference
เราจะใช้เมื่อไหร่AtomicReference
?
จำเป็นต้องสร้างวัตถุในโปรแกรมแบบมัลติเธรดทั้งหมดหรือไม่?
ระบุตัวอย่างง่ายๆที่ควรใช้ AtomicReference
คำตอบ:
ควรใช้การอ้างอิงแบบอะตอมมิกในการตั้งค่าที่คุณต้องการดำเนินการอย่างง่ายแบบอะตอมมิค (เช่นthread-safe , non-trivial) ในการอ้างอิงซึ่งการซิงโครไนซ์บนจอมอนิเตอร์ไม่เหมาะสม สมมติว่าคุณต้องการตรวจสอบเพื่อดูว่าเขตข้อมูลเฉพาะเฉพาะถ้าสถานะของวัตถุยังคงอยู่ในขณะที่คุณตรวจสอบล่าสุด:
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);
เพราะความหมายอ้างอิงอะตอมคุณสามารถทำเช่นนี้แม้ว่าวัตถุที่ใช้ร่วมกันในหมู่กระทู้โดยไม่ต้องใช้cache
synchronized
โดยทั่วไปคุณควรใช้ซิงโครไนซ์หรือjava.util.concurrent
เฟรมเวิร์กแทนที่จะดีกว่าAtomic*
เว้นแต่ว่าคุณรู้ว่ากำลังทำอะไรอยู่
การอ้างอิงต้นไม้ต้นตายที่ยอดเยี่ยมสองประการซึ่งจะแนะนำคุณในหัวข้อนี้:
โปรดทราบว่า (ฉันไม่รู้ว่าสิ่งนี้เป็นจริงเสมอ) การอ้างอิงอ้างอิง (เช่น=
) เป็นตัวอะตอม (อัปเดตชนิด 64 บิตดั้งเดิมเช่นlong
หรือdouble
อาจไม่ใช่อะตอม แต่การอัปเดตการอ้างอิงเป็นอะตอมเสมอแม้ว่าจะเป็น 64 บิต ) Atomic*
โดยไม่ต้องใช้อย่างชัดเจน
ดู3ed Java จำเพาะภาษา, มาตรา 17.7
AtomicReference
คุณควรทำเครื่องหมายตัวแปรvolatile
เพราะในขณะที่รันไทม์รับประกันว่าการมอบหมายการอ้างอิงคืออะตอมคอมไพเลอร์อาจดำเนินการปรับให้เหมาะสมภายใต้สมมติฐานว่าตัวแปรไม่ได้ถูกแก้ไขโดยเธรดอื่น
AtomicReference
"; หากคุณกำลังใช้งานอยู่คำแนะนำของฉันก็คือไปในทิศทางตรงกันข้ามและทำเครื่องหมายfinal
เพื่อให้คอมไพเลอร์สามารถปรับให้เหมาะสม
การอ้างอิงอะตอมมิกนั้นเหมาะที่จะใช้เมื่อคุณต้องการแบ่งใช้และเปลี่ยนสถานะของวัตถุที่ไม่เปลี่ยนรูปแบบระหว่างหลายเธรด นั่นเป็นคำแถลงที่มีความหนาแน่นสูงมากดังนั้นฉันจะทำลายมันลงเล็กน้อย
ก่อนวัตถุที่ไม่เปลี่ยนรูปเป็นวัตถุที่ไม่เปลี่ยนแปลงอย่างมีประสิทธิภาพหลังการก่อสร้าง บ่อยครั้งที่เมธอดของวัตถุที่ไม่เปลี่ยนรูปแบบจะส่งคืนอินสแตนซ์ใหม่ของคลาสเดียวกันนั้น บางตัวอย่างรวมคลาส wrapper ของ Long and Double และ String เพียงเพื่อตั้งชื่อให้สองสามตัว (ตามการเขียนโปรแกรมพร้อมกันใน JVMวัตถุที่ไม่เปลี่ยนรูปแบบเป็นส่วนสำคัญของการเกิดพร้อมกันที่ทันสมัย)
ถัดไปทำไม AtomicReference ดีกว่าวัตถุที่ระเหยได้สำหรับการแบ่งปันค่าที่ใช้ร่วมกัน ตัวอย่างโค้ดแบบง่ายจะแสดงความแตกต่าง
volatile String sharedValue;
static final Object lock=new Object();
void modifyString(){
synchronized(lock){
sharedValue=sharedValue+"something to add";
}
}
ทุกครั้งที่คุณต้องการแก้ไขสตริงที่อ้างอิงโดยเขตข้อมูลระเหยนั้นตามค่าปัจจุบันคุณต้องได้รับการล็อคบนวัตถุนั้นก่อน สิ่งนี้จะป้องกันไม่ให้เธรดอื่นเข้ามาในระหว่างนี้และเปลี่ยนค่าที่อยู่ตรงกลางของการต่อสตริงใหม่ จากนั้นเมื่อเธรดของคุณดำเนินการต่อคุณปิดกั้นการทำงานของเธรดอื่น แต่จริงๆแล้วรหัสนั้นจะใช้งานได้ดูสะอาดตาและจะทำให้คนส่วนใหญ่มีความสุข
ปัญหาเล็กน้อย มันช้า โดยเฉพาะอย่างยิ่งหากมีการโต้แย้งจำนวนมากของวัตถุล็อคนั้น นั่นเป็นเพราะการล็อคส่วนใหญ่ต้องการการเรียกใช้ระบบปฏิบัติการและเธรดของคุณจะบล็อกและเปลี่ยนบริบทจาก CPU เพื่อหลีกทางให้กระบวนการอื่น ๆ
ตัวเลือกอื่นคือใช้ 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);
}
ตอนนี้ทำไมถึงดีกว่า จริงๆแล้วรหัสนั้นสะอาดน้อยกว่าเดิมเล็กน้อย แต่มีบางสิ่งที่สำคัญจริงๆที่เกิดขึ้นภายใต้ประทุนใน AtomicRefrence และนั่นคือการเปรียบเทียบและสลับ มันเป็นคำสั่ง CPU เดียวไม่ใช่การเรียกใช้ระบบปฏิบัติการที่ทำให้สวิตช์เกิดขึ้น นั่นเป็นคำสั่งเดียวของ CPU และเนื่องจากไม่มีการล็อคจึงไม่มีการสลับบริบทในกรณีที่การล็อคถูกใช้ซึ่งช่วยประหยัดเวลาได้มากขึ้น!
การดักจับคือสำหรับ AtomicReferences สิ่งนี้ไม่ได้ใช้การเรียก. Equals () แต่เป็นการเปรียบเทียบ == สำหรับค่าที่คาดไว้ ดังนั้นตรวจสอบให้แน่ใจว่าสิ่งที่คาดหวังคือวัตถุจริงที่ส่งคืนมาจากรับในลูป
worked
เพื่อให้ได้ความหมายเดียวกัน
นี่คือกรณีการใช้งานสำหรับ AtomicReference:
พิจารณาคลาสนี้ที่ทำหน้าที่เป็นช่วงตัวเลขและใช้ตัวแปร AtmomicInteger แต่ละตัวเพื่อรักษาขอบเขตจำนวนต่ำและสูง
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());
}
}
ทั้ง setLower และ setUpper เป็นลำดับการตรวจสอบจากนั้นทำหน้าที่ แต่พวกเขาไม่ได้ใช้การล็อคที่เพียงพอเพื่อให้พวกเขาอะตอม หากช่วงหมายเลขถือ (0, 10) และหนึ่งเธรดเรียก setLower (5) ในขณะที่เธรดอื่นเรียก setUpper (4) ด้วยช่วงเวลาที่โชคไม่ดีทั้งคู่จะผ่านการตรวจสอบในตัวตั้งค่าและการแก้ไขทั้งสองจะถูกนำไปใช้ ผลลัพธ์คือช่วงนี้มีสถานะไม่ถูกต้อง (5, 4) ดังนั้นในขณะที่ AtomicIntegers ที่อยู่ภายใต้เธรดปลอดภัยคลาสคอมโพสิตไม่ สามารถแก้ไขได้โดยใช้ AtomicReference แทนการใช้ AtomicIntegers แต่ละรายการสำหรับขอบเขตบนและล่าง
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;
}
}
}
คุณสามารถใช้ AtomicReference เมื่อใช้การล็อคในแง่ดี คุณมีวัตถุที่ใช้ร่วมกันและคุณต้องการเปลี่ยนจากเธรดมากกว่า 1 รายการ
เนื่องจากเธรดอื่นอาจแก้ไขและ / สามารถแก้ไขระหว่าง 2 ขั้นตอนเหล่านี้ คุณต้องทำมันในการปฏิบัติการปรมาณู นี่คือสิ่งที่ AtomicReference สามารถช่วยได้
นี่คือกรณีการใช้งานที่ง่ายมากและไม่เกี่ยวข้องกับความปลอดภัยของเธรด
ในการแชร์วัตถุระหว่างการร้องขอแลมบ์ดาAtomicReference
ตัวเลือกคือ:
public void doSomethingUsingLambdas() {
AtomicReference<YourObject> yourObjectRef = new AtomicReference<>();
soSomethingThatTakesALambda(() -> {
yourObjectRef.set(youObject);
});
soSomethingElseThatTakesALambda(() -> {
YourObject yourObject = yourObjectRef.get();
});
}
ฉันไม่ได้บอกว่านี่คือการออกแบบที่ดีหรืออะไรก็ตาม (มันเป็นเพียงตัวอย่างเล็กน้อย) แต่ถ้าคุณมีกรณีที่คุณต้องการแชร์วัตถุระหว่างการขอแลมบ์ดา AtomicReference
นั่นเป็นตัวเลือก
ในความเป็นจริงคุณสามารถใช้วัตถุใด ๆ ที่เก็บข้อมูลอ้างอิงแม้กระทั่งชุดรวมที่มีเพียงรายการเดียว อย่างไรก็ตาม AtomicReference นั้นเหมาะสมอย่างยิ่ง
ฉันจะไม่พูดอะไรมาก เพื่อนเพื่อนที่เคารพนับถือของฉันได้ให้ความเห็นที่มีค่ารหัสที่ใช้งานได้อย่างสมบูรณ์ในตอนท้ายของบล็อกนี้ควรลบความสับสนใด ๆ มันเกี่ยวกับการจองที่นั่งภาพยนตร์ขนาดเล็กในสถานการณ์แบบมัลติเธรด
ข้อเท็จจริงเบื้องต้นที่สำคัญบางประการมีดังนี้ 1> เธรดที่แตกต่างกันสามารถแข่งขันได้กับอินสแตนซ์และตัวแปรสมาชิกแบบคงที่ในพื้นที่ฮีพ 2> การอ่านหรือเขียนแบบระเหยนั้นมีทั้งอะตอมและต่อเนื่อง / เกิดขึ้นก่อนและจากหน่วยความจำเท่านั้น ด้วยการพูดแบบนี้ฉันหมายความว่าการอ่านใด ๆ จะเป็นไปตามการเขียนในหน่วยความจำก่อนหน้า และการเขียนใด ๆ จะติดตามอ่านจากหน่วยความจำก่อนหน้า ดังนั้นเธรดใด ๆ ที่ทำงานกับสารระเหยจะเห็นค่าที่ทันสมัยที่สุดเสมอ AtomicReference ใช้คุณสมบัติของสารระเหย
ต่อไปนี้เป็นซอร์สโค้ดบางส่วนของ AtomicReference AtomicReference อ้างถึงการอ้างอิงวัตถุ การอ้างอิงนี้เป็นตัวแปรสมาชิกระเหยในอินสแตนซ์ AtomicReference ดังต่อไปนี้
private volatile V value;
รับ () เพียงแค่ส่งกลับค่าล่าสุดของตัวแปร (เช่น volatiles ทำในลักษณะ "เกิดขึ้นก่อน")
public final V get()
ต่อไปนี้เป็นวิธีที่สำคัญที่สุดของ AtomicReference
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
เมธอด comparAndSet (expect, update) เรียกเมธอด comparAndSwapObject () ของคลาสที่ไม่ปลอดภัยของ Java การเรียกเมธอดที่ไม่ปลอดภัยนี้เป็นการเรียกใช้การเรียกดั้งเดิมซึ่งจะเรียกใช้คำสั่งเดียวกับตัวประมวลผล "คาดหวัง" และ "อัปเดต" อ้างอิงแต่ละวัตถุ
หากและถ้าหากสมาชิกอินสแตนซ์ของตัวแปร AtomicReference "ค่า" อ้างอิงถึงวัตถุเดียวกันนั้นถูกอ้างถึงโดย "expect", "update" จะถูกกำหนดให้กับตัวแปรอินสแตนซ์นี้ทันทีและจะส่งคืน "true" มิฉะนั้นจะส่งคืน false สิ่งทั้งหมดทำแบบอะตอม ไม่มีเธรดอื่นใดสามารถดักจับได้ เนื่องจากนี่เป็นการดำเนินการตัวประมวลผลเดียว (ความมหัศจรรย์ของสถาปัตยกรรมคอมพิวเตอร์สมัยใหม่) จึงมักจะเร็วกว่าการใช้บล็อกแบบซิงโครไนซ์ แต่โปรดจำไว้ว่าเมื่อจำเป็นต้องอัปเดตตัวแปรหลายตัวตามปกติ AtomicReference จะไม่ช่วยได้
ฉันต้องการเพิ่มรหัสการรันแบบเต็มซึ่งสามารถเรียกใช้ใน eclipse มันจะขจัดความสับสนมากมาย ที่นี่ผู้ใช้ 22 คน (เธรด MyTh) พยายามจอง 20 ที่นั่ง ต่อไปนี้เป็นข้อมูลโค้ดตามด้วยรหัสเต็ม
ข้อมูลโค้ดที่ผู้ใช้ 22 คนพยายามจอง 20 ที่นั่ง
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();
}
ต่อไปนี้เป็นรหัสทำงานเต็ม
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
}
}
}
}
เราจะใช้ AtomicReference เมื่อใด
AtomicReferenceเป็นวิธีที่ยืดหยุ่นในการอัปเดตค่าตัวแปรแบบอะตอมมิกโดยไม่ต้องใช้การซิงโครไนซ์
AtomicReference
รองรับการตั้งโปรแกรมล็อคเธรดที่ไม่มีล็อคในตัวแปรเดี่ยว
มีหลายวิธีในการบรรลุความปลอดภัยของเธรดด้วยAPI พร้อมกันในระดับสูง ตัวแปรอะตอมเป็นหนึ่งในหลายตัวเลือก
Lock
วัตถุที่รองรับการล็อคสำนวนที่ทำให้การใช้งานพร้อมกันจำนวนมากง่ายขึ้น
Executors
กำหนด API ระดับสูงสำหรับการเรียกใช้และการจัดการเธรด การใช้งาน Executor ที่จัดทำโดย java.util.concurrent จัดเตรียมการจัดการเธรดพูลที่เหมาะสำหรับแอ็พพลิเคชันขนาดใหญ่
การรวบรวมพร้อมกันทำให้การจัดการข้อมูลจำนวนมากง่ายขึ้นและสามารถลดความต้องการในการซิงโครไนซ์ได้อย่างมาก
ตัวแปรอะตอมมิกมีคุณสมบัติที่ลดการซิงโครไนซ์และช่วยหลีกเลี่ยงข้อผิดพลาดที่สอดคล้องกันของหน่วยความจำ
ระบุตัวอย่างง่ายๆที่ควรใช้ AtomicReference
รหัสตัวอย่างด้วย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);
จำเป็นต้องสร้างวัตถุในโปรแกรมแบบมัลติเธรดทั้งหมดหรือไม่?
คุณไม่จำเป็นต้องใช้AtomicReference
ในโปรแกรมแบบมัลติเธรดทั้งหมด
AtomicReference
หากคุณต้องการที่จะป้องกันตัวแปรเดียวใช้ หากคุณต้องการป้องกันบล็อครหัสให้ใช้โครงสร้างอื่น ๆ เช่นLock
/ synchronized
ฯลฯ
อีกตัวอย่างง่าย ๆ คือทำการปรับเปลี่ยนเธรดที่ปลอดภัยในวัตถุเซสชัน
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;
}
}
ที่มา: http://www.ibm.com/developerworks/library/j-jtp09238/index.html