คุณกำลังถามว่าพวกเขาทำงานอย่างไรภายในโดยเฉพาะดังนั้นคุณอยู่ที่นี่:
ไม่มีการซิงโครไนซ์
private int counter;
public int getNextUniqueIndex() {
return counter++;
}
โดยทั่วไปจะอ่านค่าจากหน่วยความจำเพิ่มขึ้นและกลับไปสู่หน่วยความจำ สิ่งนี้ใช้ได้ในเธรดเดียว แต่ทุกวันนี้ในยุคของมัลติคอร์มัลติซีพียูแคชหลายระดับจะทำงานไม่ถูกต้อง ก่อนอื่นจะแนะนำสภาพการแข่งขัน (หลายกระทู้สามารถอ่านค่าในเวลาเดียวกัน) แต่ยังมีปัญหาการมองเห็น ค่าอาจถูกเก็บไว้ในหน่วยความจำซีพียู " ท้องถิ่น " (บางแคช) และไม่สามารถมองเห็นได้สำหรับซีพียู / คอร์อื่น (และ - เธรด) นี่คือสาเหตุที่หลายคนอ้างถึงสำเนาของตัวแปรในเครื่อง มันไม่ปลอดภัยมาก พิจารณารหัสการหยุดเธรดที่เป็นที่นิยม แต่ใช้งานไม่ได้นี้:
private boolean stopped;
public void run() {
while(!stopped) {
//do some work
}
}
public void pleaseStop() {
stopped = true;
}
เพิ่มvolatile
ไปที่stopped
ตัวแปรและใช้งานได้ดี - หากเธรดอื่น ๆ แก้ไขstopped
ตัวแปรผ่านpleaseStop()
วิธีการคุณจะรับประกันว่าจะเห็นการเปลี่ยนแปลงนั้นทันทีในwhile(!stopped)
ลูปของเธรดการทำงาน BTW นี้ไม่ได้เป็นวิธีที่ดีที่จะขัดขวางด้ายทั้งดู: วิธีการหยุดด้ายที่ใช้ตลอดไปโดยไม่ต้องใช้ใด ๆและหยุดด้ายเฉพาะจาวา
AtomicInteger
private AtomicInteger counter = new AtomicInteger();
public int getNextUniqueIndex() {
return counter.getAndIncrement();
}
AtomicInteger
ใช้ระดับ CAS ( เปรียบเทียบและ-swap ) การดำเนินงานในระดับต่ำ CPU (ไม่จำเป็นต้องประสาน!) พวกเขาช่วยให้คุณสามารถปรับเปลี่ยนตัวแปรโดยเฉพาะอย่างยิ่งเฉพาะในกรณีที่มูลค่าปัจจุบันเท่ากับสิ่งอื่น (และถูกส่งกลับประสบความสำเร็จ) ดังนั้นเมื่อคุณรันgetAndIncrement()
มันจะทำงานเป็นวง (การใช้จริงที่ง่าย):
int current;
do {
current = get();
} while(!compareAndSet(current, current + 1));
ดังนั้นโดยทั่วไป: อ่าน; พยายามเก็บค่าที่เพิ่มขึ้น หากไม่สำเร็จ (ค่าไม่เท่ากับcurrent
) ให้อ่านและลองอีกครั้ง compareAndSet()
จะดำเนินการในรหัสพื้นเมือง (งานประกอบ)
volatile
โดยไม่ต้องประสาน
private volatile int counter;
public int getNextUniqueIndex() {
return counter++;
}
รหัสนี้ไม่ถูกต้อง มันแก้ไขปัญหาการมองเห็น ( volatile
ทำให้แน่ใจว่ากระทู้อื่น ๆ สามารถเห็นการเปลี่ยนแปลงที่เกิดขึ้นcounter
) แต่ยังคงมีสภาพการแข่งขัน สิ่งนี้ได้รับการอธิบายหลาย ๆ ครั้ง: การเพิ่มขึ้นก่อน / หลังไม่ได้เป็นแบบอะตอม
ผลข้างเคียงเพียงอย่างเดียวของvolatile
คือการ " ล้างข้อมูล " แคชเพื่อให้บุคคลอื่นทั้งหมดเห็นข้อมูลเวอร์ชันใหม่ที่สุด นี่เข้มงวดเกินไปในสถานการณ์ส่วนใหญ่ นั่นคือเหตุผลที่volatile
ไม่ได้เป็นค่าเริ่มต้น
volatile
ไม่มีการประสาน (2)
volatile int i = 0;
void incIBy5() {
i += 5;
}
ปัญหาเช่นเดียวกับข้างต้น แต่ยิ่งเลวร้ายลงเพราะไม่ได้เป็นi
private
สภาพการแข่งขันยังคงมีอยู่ ทำไมถึงเป็นปัญหา? ถ้าพูดสองหัวข้อเรียกใช้รหัสนี้ไปพร้อม ๆ กันการส่งออกอาจจะมีหรือ+ 5
+ 10
อย่างไรก็ตามคุณรับประกันว่าจะเห็นการเปลี่ยนแปลง
อิสระหลายอย่าง synchronized
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
แปลกใจที่รหัสนี้ไม่ถูกต้องเช่นกัน ความจริงมันผิดทั้งหมด ก่อนอื่นคุณกำลังทำการซิงโครไนซ์i
ซึ่งกำลังจะมีการเปลี่ยนแปลง (ยิ่งไปกว่านั้นi
คือดึกดำบรรพ์ดังนั้นฉันเดาว่าคุณกำลังซิงโครไนซ์กับชั่วคราวที่Integer
สร้างขึ้นผ่านออโต้บ็อกซ์ ... ) มีข้อบกพร่องอย่างสมบูรณ์ คุณสามารถเขียน:
synchronized(new Object()) {
//thread-safe, SRSLy?
}
ไม่มีสองหัวข้อสามารถป้อนเดียวกันsynchronized
บล็อกที่มีการล็อคเดียวกัน ในกรณีนี้ (และในทำนองเดียวกันในรหัสของคุณ) วัตถุล็อคจะเปลี่ยนไปตามการดำเนินการทุกครั้งดังนั้นจึงsynchronized
ไม่มีผล
แม้ว่าคุณจะใช้ตัวแปรสุดท้าย (หรือthis
) สำหรับการซิงโครไนซ์ แต่รหัสก็ยังไม่ถูกต้อง สองเธรดสามารถอ่านi
เพื่อtemp
ซิงโครนัส (มีค่าเดียวกันในเครื่องtemp
) จากนั้นเธรดแรกกำหนดค่าใหม่ให้กับi
(พูดจาก 1 ถึง 6) และอีกอันหนึ่งทำสิ่งเดียวกัน (จาก 1 ถึง 6)
การซิงโครไนซ์ต้องครอบคลุมตั้งแต่การอ่านจนถึงการกำหนดค่า การซิงโครไนซ์ครั้งแรกของคุณไม่มีผลกระทบ (การอ่านint
คืออะตอมมิก) และวินาทีเช่นกัน ในความคิดของฉันเหล่านี้เป็นรูปแบบที่ถูกต้อง:
void synchronized incIBy5() {
i += 5
}
void incIBy5() {
synchronized(this) {
i += 5
}
}
void incIBy5() {
synchronized(this) {
int temp = i;
i = temp + 5;
}
}