ใครสามารถบอกข้อดีของวิธีการซิงโครไนซ์กับบล็อกซิงโครไนซ์กับตัวอย่างได้หรือไม่?
ใครสามารถบอกข้อดีของวิธีการซิงโครไนซ์กับบล็อกซิงโครไนซ์กับตัวอย่างได้หรือไม่?
คำตอบ:
ใครสามารถบอกฉันถึงข้อดีของวิธีการซิงโครไนซ์บล็อกที่ซิงโครไนซ์ด้วยตัวอย่างได้หรือไม่ ขอบคุณ
ไม่มีข้อได้เปรียบที่ชัดเจนของการใช้วิธีการซิงโครไนซ์กับบล็อก
บางทีอาจจะเป็นเพียงคนเดียว ( แต่ผมจะไม่เรียกว่าการพิจารณาเป็นพิเศษ) this
คือคุณไม่จำเป็นต้องมีการอ้างอิงวัตถุ
วิธี:
public synchronized void method() { // blocks "this" from here....
...
...
...
} // to here
บล็อก:
public void method() {
synchronized( this ) { // blocks "this" from here ....
....
....
....
} // to here...
}
ดู? ไม่มีข้อได้เปรียบเลย
บล็อกจะมีข้อได้เปรียบกว่าวิธีการ แต่ส่วนใหญ่อยู่ในความยืดหยุ่นเพราะคุณสามารถใช้วัตถุอื่นเป็นล็อคในขณะที่วิธีการซิงค์จะล็อควัตถุทั้งหมด
เปรียบเทียบ:
// locks the whole object
...
private synchronized void someInputRelatedWork() {
...
}
private synchronized void someOutputRelatedWork() {
...
}
เมื่อเทียบกับ
// Using specific locks
Object inputLock = new Object();
Object outputLock = new Object();
private void someInputRelatedWork() {
synchronized(inputLock) {
...
}
}
private void someOutputRelatedWork() {
synchronized(outputLock) {
...
}
}
นอกจากนี้หากวิธีการเติบโตคุณยังคงสามารถแยกส่วนที่ซิงโครไนซ์ได้:
private void method() {
... code here
... code here
... code here
synchronized( lock ) {
... very few lines of code here
}
... code here
... code here
... code here
... code here
}
synchronized
บล็อกจะดำเนินการโดยใช้สองคำแนะนำmonitorenter
และmonitorexit
, บวกการจัดการข้อยกเว้นซึ่งมั่นใจว่าmonitorexit
จะเรียกว่าแม้ในกรณีพิเศษ นั่นคือทั้งหมดที่บันทึกไว้เมื่อใช้synchronized
วิธีการ
ข้อแตกต่างที่แท้จริงคือบล็อกที่ซิงโครไนซ์สามารถเลือกวัตถุที่จะซิงโครไนซ์ วิธีการซิงโครไนซ์สามารถใช้ได้'this'
(หรืออินสแตนซ์ของคลาสที่สอดคล้องกันสำหรับวิธีคลาสที่ซิงโครไนซ์เท่านั้น) ตัวอย่างเช่นสิ่งเหล่านี้เทียบเท่าความหมาย:
synchronized void foo() {
...
}
void foo() {
synchronized (this) {
...
}
}
หลังมีความยืดหยุ่นมากขึ้นเนื่องจากสามารถแข่งขันเพื่อล็อคการเชื่อมโยงของวัตถุใด ๆซึ่งมักเป็นตัวแปรสมาชิก มีความละเอียดมากขึ้นเนื่องจากคุณสามารถมีรหัสที่ทำงานพร้อมกันก่อนและหลังการบล็อก แต่ยังอยู่ในเมธอด แน่นอนว่าคุณสามารถใช้วิธีการซิงโครไนซ์ได้อย่างง่ายดายโดยการเปลี่ยนรหัสที่เกิดขึ้นพร้อมกันเป็นวิธีที่ไม่ซิงโครไนซ์แยกต่างหาก ใช้วิธีใดก็ได้ที่ทำให้รหัสเข้าใจได้มากขึ้น
ข้อดี:
จุดด้อย:
ข้อดี:
จุดด้อย:
ส่วนตัวแล้วฉันชอบใช้วิธีการซิงโครไนซ์กับคลาสที่เน้นเฉพาะสิ่งที่ต้องการการซิงโครไนซ์เท่านั้น คลาสดังกล่าวควรมีขนาดเล็กที่สุดและควรตรวจสอบการซิงโครไนซ์ได้ง่าย คนอื่นไม่จำเป็นต้องสนใจเรื่องการซิงโครไนซ์
ความแตกต่างที่สำคัญคือถ้าคุณใช้บล็อกที่ซิงโครไนซ์คุณอาจล็อควัตถุอื่นนอกเหนือจากนี้ซึ่งทำให้ยืดหยุ่นได้มากกว่า
สมมติว่าคุณมีคิวข้อความและผู้ผลิตข้อความและผู้บริโภคหลายคน เราไม่ต้องการให้ผู้ผลิตรบกวนซึ่งกันและกัน แต่ผู้บริโภคควรสามารถดึงข้อความได้โดยไม่ต้องรอผู้ผลิต ดังนั้นเราแค่สร้างวัตถุ
Object writeLock = new Object();
และต่อจากนี้ไปทุกครั้งที่ผู้ผลิตต้องการเพิ่มข้อความใหม่เราเพิ่งล็อคข้อความนั้น:
synchronized(writeLock){
// do something
}
ดังนั้นผู้บริโภคอาจยังอ่านและผู้ผลิตจะถูกล็อค
วิธีการซิงโครไนซ์
วิธีการซิงโครไนซ์มีสองผล
ก่อนอื่นเมื่อเธรดหนึ่งกำลังดำเนินการวิธีการซิงโครไนซ์สำหรับวัตถุเธรดอื่นทั้งหมดที่เรียกใช้วิธีการซิงโครไนส์สำหรับบล็อกวัตถุเดียวกัน (ระงับการดำเนินการ) จนกระทั่งเธรดแรกเสร็จสิ้นด้วยวัตถุ
ประการที่สองเมื่อเมธอดซิงโครไนซ์ออกแล้วมันจะสร้างความสัมพันธ์ที่เกิดขึ้นก่อนโดยอัตโนมัติกับการเรียกใช้เมธอดซิงโครไนซ์ที่ตามมาสำหรับวัตถุเดียวกัน สิ่งนี้รับประกันได้ว่าการเปลี่ยนแปลงสถานะของวัตถุสามารถมองเห็นได้โดยเธรดทั้งหมด
โปรดทราบว่าตัวสร้างไม่สามารถซิงโครไนซ์ได้ - การใช้คีย์เวิร์ดที่ซิงโครไนซ์กับนวกรรมิกเป็นข้อผิดพลาดทางไวยากรณ์ การซิงโครไนซ์คอนสตรัคเตอร์ไม่สมเหตุสมผลเนื่องจากเฉพาะเธรดที่สร้างวัตถุที่ควรเข้าถึงในขณะที่มันกำลังถูกสร้างขึ้น
งบประสาน
ซึ่งแตกต่างจากวิธีการทำข้อมูลให้ตรงกันคำสั่งตรงกันจะต้องระบุวัตถุที่ให้ล็อคที่แท้จริง: ฉันมักจะใช้สิ่งนี้เพื่อประสานการเข้าถึงรายการหรือแผนที่ แต่ฉันไม่ต้องการที่จะปิดกั้นการเข้าถึงวิธีการทั้งหมดของวัตถุ
Q: การล็อคภายในและการซิงโครไนซ์การซิงโครไนซ์ถูกสร้างขึ้นรอบเอนทิตีภายในที่เรียกว่าการล็อคอินทรินหรือล็อคการตรวจสอบ (ข้อมูลจำเพาะ API มักจะอ้างถึงเอนทิตีนี้เพียงแค่เป็น "มอนิเตอร์") การล็อกที่แท้จริงมีบทบาทในการซิงโครไนซ์ทั้งสองด้าน: บังคับใช้การเข้าถึงเอกสิทธิ์ของสถานะของวัตถุและสร้างความสัมพันธ์ก่อนเกิดขึ้น
วัตถุทุกชิ้นมีการล็อคที่แท้จริงที่เกี่ยวข้อง โดยการประชุมเธรดที่ต้องการการเข้าถึงแบบเอกสิทธิ์เฉพาะบุคคลและสอดคล้องกับเขตข้อมูลของวัตถุจะต้องได้รับการล็อคที่แท้จริงของวัตถุก่อนที่จะเข้าถึงพวกเขาแล้วปล่อยล็อคที่แท้จริงเมื่อมันทำกับพวกเขา มีการกล่าวถึงเธรดเพื่อให้เป็นเจ้าของล็อคที่แท้จริงระหว่างเวลาที่ได้รับการล็อคและปลดล็อค ตราบใดที่เธรดเป็นเจ้าของการล็อคที่แท้จริงไม่มีเธรดอื่นสามารถรับการล็อคเดียวกัน เธรดอื่นจะบล็อกเมื่อพยายามรับการล็อก
package test;
public class SynchTest implements Runnable {
private int c = 0;
public static void main(String[] args) {
new SynchTest().test();
}
public void test() {
// Create the object with the run() method
Runnable runnable = new SynchTest();
Runnable runnable2 = new SynchTest();
// Create the thread supplying it with the runnable object
Thread thread = new Thread(runnable,"thread-1");
Thread thread2 = new Thread(runnable,"thread-2");
// Here the key point is passing same object, if you pass runnable2 for thread2,
// then its not applicable for synchronization test and that wont give expected
// output Synchronization method means "it is not possible for two invocations
// of synchronized methods on the same object to interleave"
// Start the thread
thread.start();
thread2.start();
}
public synchronized void increment() {
System.out.println("Begin thread " + Thread.currentThread().getName());
System.out.println(this.hashCode() + "Value of C = " + c);
// If we uncomment this for synchronized block, then the result would be different
// synchronized(this) {
for (int i = 0; i < 9999999; i++) {
c += i;
}
// }
System.out.println("End thread " + Thread.currentThread().getName());
}
// public synchronized void decrement() {
// System.out.println("Decrement " + Thread.currentThread().getName());
// }
public int value() {
return c;
}
@Override
public void run() {
this.increment();
}
}
ตรวจสอบผลลัพธ์ที่ต่างกันด้วยวิธีการซิงโครไนซ์บล็อกและไม่มีการซิงโครไนซ์
หมายเหตุ: วิธีการแบบซิงโครไนซ์แบบคงที่และบล็อกทำงานบนวัตถุคลาส
public class MyClass {
// locks MyClass.class
public static synchronized void foo() {
// do something
}
// similar
public static void foo() {
synchronized(MyClass.class) {
// do something
}
}
}
เมื่อคอมไพเลอร์ java แปลงซอร์สโค้ดของคุณเป็นโค้ดไบต์มันจะจัดการวิธีการซิงโครไนซ์และบล็อกที่ซิงโครไนซ์แตกต่างกันมาก
เมื่อ JVM เรียกใช้งานเมธอดที่ซิงโครไนซ์เธรดการเรียกใช้งานจะระบุว่าโครงสร้าง method_info ของเมธอดนั้นมีชุดแฟล็ก ACC_SYNCHRONIZED จากนั้นจะรับล็อกของวัตถุโดยอัตโนมัติเรียกวิธีการและปลดล็อค หากมีข้อยกเว้นเกิดขึ้นเธรดจะปลดล็อกโดยอัตโนมัติ
การซิงโครไนซ์บล็อกวิธีในทางตรงกันข้ามข้ามการสนับสนุนในตัวของ JVM สำหรับการรับล็อคของวัตถุและการจัดการข้อยกเว้นและต้องการให้ฟังก์ชั่นการเขียนอย่างชัดเจนในรหัสไบต์ หากคุณอ่านรหัสไบต์สำหรับวิธีการที่มีการบล็อกแบบซิงโครไนซ์คุณจะเห็นการดำเนินการเพิ่มเติมมากกว่าโหลเพื่อจัดการฟังก์ชันการทำงานนี้
สิ่งนี้แสดงให้เห็นการเรียกเพื่อสร้างทั้งวิธีการซิงโครไนซ์และบล็อกซิงโครไนซ์:
public class SynchronizationExample {
private int i;
public synchronized int synchronizedMethodGet() {
return i;
}
public int synchronizedBlockGet() {
synchronized( this ) {
return i;
}
}
}
synchronizedMethodGet()
วิธีการสร้างรหัสไบต์ต่อไปนี้:
0: aload_0
1: getfield
2: nop
3: iconst_m1
4: ireturn
และนี่คือรหัสไบต์จากsynchronizedBlockGet()
วิธีการ:
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: aload_0
5: getfield
6: nop
7: iconst_m1
8: aload_1
9: monitorexit
10: ireturn
11: astore_2
12: aload_1
13: monitorexit
14: aload_2
15: athrow
ความแตกต่างที่สำคัญอย่างหนึ่งระหว่างวิธีการซิงโครไนซ์กับบล็อกคือบล็อกแบบซิงโครไนซ์โดยทั่วไปจะลดขอบเขตการล็อค เนื่องจากขอบเขตของการล็อกนั้นแปรผันตามประสิทธิภาพการทำงานจึงดีกว่าเสมอในการล็อคเฉพาะส่วนที่สำคัญของรหัส หนึ่งในตัวอย่างที่ดีที่สุดของการใช้ซิงโครไนซ์บล็อกคือการตรวจสอบการล็อคสองครั้งในรูปแบบซิงเกิลที่แทนที่จะล็อคทั้งgetInstance()
วิธีเราจะล็อคเฉพาะส่วนที่สำคัญของรหัส สิ่งนี้ช่วยปรับปรุงประสิทธิภาพได้อย่างมากเนื่องจากต้องใช้การล็อกเพียงหนึ่งหรือสองครั้ง
ในขณะที่ใช้วิธีการซิงโครไนซ์คุณจะต้องระมัดระวังเป็นพิเศษหากคุณผสมทั้งวิธีการซิงโครไนซ์แบบคงที่และไม่คงที่
monitorenter
และmonitorexit
ก่อนที่จะเรียกใช้รหัส
บ่อยครั้งที่ฉันใช้สิ่งนี้เพื่อซิงโครไนซ์การเข้าถึงรายการหรือแผนที่ แต่ฉันไม่ต้องการปิดกั้นการเข้าถึงวิธีการทั้งหมดของวัตถุ
ในโค้ดต่อไปนี้หนึ่งเธรดที่แก้ไขรายการจะไม่บล็อกรอเธรดที่กำลังแก้ไขแผนที่ หากวิธีการทำข้อมูลให้ตรงกันบนวัตถุดังนั้นแต่ละวิธีจะต้องรอแม้ว่าการแก้ไขที่ทำไว้จะไม่ขัดแย้งกัน
private List<Foo> myList = new ArrayList<Foo>();
private Map<String,Bar) myMap = new HashMap<String,Bar>();
public void put( String s, Bar b ) {
synchronized( myMap ) {
myMap.put( s,b );
// then some thing that may take a while like a database access or RPC or notifying listeners
}
}
public void hasKey( String s, ) {
synchronized( myMap ) {
myMap.hasKey( s );
}
}
public void add( Foo f ) {
synchronized( myList ) {
myList.add( f );
// then some thing that may take a while like a database access or RPC or notifying listeners
}
}
public Thing getMedianFoo() {
Foo med = null;
synchronized( myList ) {
Collections.sort(myList);
med = myList.get(myList.size()/2);
}
return med;
}
ด้วยบล็อกที่ซิงโครไนซ์คุณสามารถมีซิงโครไนซ์หลายตัวเพื่อให้สามารถทำสิ่งต่าง ๆ พร้อมกัน แต่ไม่ขัดแย้งกันในเวลาเดียวกัน
วิธีการซิงโครไนซ์สามารถตรวจสอบได้โดยใช้ reflection API ซึ่งจะเป็นประโยชน์สำหรับการทดสอบสัญญาบางอย่างเช่นวิธีการทั้งหมดในรูปแบบที่ตรงกัน
ตัวอย่างต่อไปนี้พิมพ์วิธีการซิงโครไนซ์ทั้งหมดของ Hashtable:
for (Method m : Hashtable.class.getMethods()) {
if (Modifier.isSynchronized(m.getModifiers())) {
System.out.println(m);
}
}
หมายเหตุสำคัญเกี่ยวกับการใช้บล็อกที่ซิงโครไนซ์: ระวังสิ่งที่คุณใช้เป็นวัตถุล็อค!
ข้อมูลโค้ดจากผู้ใช้ 2277816 ด้านบนแสดงจุดนี้ว่าการอ้างอิงถึงตัวอักษรสตริงถูกใช้เป็นวัตถุล็อค ตระหนักดีว่าตัวอักษรสตริงจะถูก interned โดยอัตโนมัติใน Java และคุณควรเริ่มเห็นปัญหา: ทุกส่วนของรหัสที่ประสานกับ "ล็อค" ตัวอักษรแบ่งปันล็อคเดียวกัน! สิ่งนี้สามารถนำไปสู่การหยุดชะงักได้อย่างง่ายดายด้วยโค้ดที่ไม่เกี่ยวข้องอย่างสมบูรณ์
มันไม่ได้เป็นเพียงวัตถุ String ที่คุณต้องระวัง การบรรจุแบบดั้งเดิมก็เป็นอันตรายเช่นกันเนื่องจากการ autoboxing และวิธีการ valueOf สามารถนำวัตถุเดียวกันกลับมาใช้ใหม่ได้ขึ้นอยู่กับค่า
สำหรับข้อมูลเพิ่มเติมโปรดดูที่: https://www.securecoding.cert.org/confluence/display/java/LCK01-J.+Do+not+synchronize+on+objects+that+may+be+reused
บ่อยครั้งที่การใช้การล็อกในระดับวิธีนั้นหยาบคายเกินไป เหตุใดจึงต้องล็อครหัสบางส่วนที่ไม่สามารถเข้าถึงทรัพยากรที่ใช้ร่วมกันได้โดยการล็อกวิธีการทั้งหมด เนื่องจากแต่ละวัตถุมีการล็อคคุณสามารถสร้างวัตถุจำลองเพื่อใช้การซิงโครไนซ์ระดับบล็อก ระดับบล็อกมีประสิทธิภาพมากขึ้นเพราะไม่ได้ล็อควิธีการทั้งหมด
นี่คือตัวอย่าง
ระดับวิธีการ
class MethodLevel {
//shared among threads
SharedResource x, y ;
public void synchronized method1() {
//multiple threads can't access
}
public void synchronized method2() {
//multiple threads can't access
}
public void method3() {
//not synchronized
//multiple threads can access
}
}
ระดับบล็อก
class BlockLevel {
//shared among threads
SharedResource x, y ;
//dummy objects for locking
Object xLock = new Object();
Object yLock = new Object();
public void method1() {
synchronized(xLock){
//access x here. thread safe
}
//do something here but don't use SharedResource x, y
// because will not be thread-safe
synchronized(xLock) {
synchronized(yLock) {
//access x,y here. thread safe
}
}
//do something here but don't use SharedResource x, y
//because will not be thread-safe
}//end of method1
}
[แก้ไข]
สำหรับCollection
ไลค์Vector
และHashtable
พวกมันจะซิงโครไนซ์เมื่อArrayList
หรือHashMap
ไม่และคุณต้องตั้งค่าคีย์เวิร์ดที่ซิงโครไนซ์หรือเรียกใช้เมธอด Collections ที่ซิงโครไนซ์:
Map myMap = Collections.synchronizedMap (myMap); // single lock for the entire map
List myList = Collections.synchronizedList (myList); // single lock for the entire list
ความแตกต่างเพียงอย่างเดียว: บล็อกที่ซิงโครไนซ์อนุญาตให้ล็อกแบบละเอียดซึ่งต่างจากวิธีการซิงโครไนซ์
โดยทั่วไปsynchronized
บล็อกหรือวิธีการถูกใช้เพื่อเขียนรหัสปลอดภัยของเธรดโดยหลีกเลี่ยงข้อผิดพลาดที่ไม่สอดคล้องกันของหน่วยความจำ
คำถามนี้เก่ามากและมีการเปลี่ยนแปลงหลายอย่างในช่วง 7 ปีที่ผ่านมา โครงสร้างการเขียนโปรแกรมใหม่ได้รับการแนะนำเพื่อความปลอดภัยของเธรด
คุณสามารถบรรลุความปลอดภัยของเธรดโดยใช้ API การทำงานพร้อมกันขั้นสูงแทนsynchronied
บล็อก หน้าเอกสารนี้ให้โครงสร้างการเขียนโปรแกรมที่ดีเพื่อความปลอดภัยของเธรด
Lock Objectsรองรับการล็อกสำนวนที่ทำให้แอพพลิเคชั่นหลาย ๆ
ผู้บริหารกำหนด API ระดับสูงสำหรับการเรียกใช้และการจัดการเธรด การปรับใช้ Executor ที่จัดทำโดย java.util.concurrent จัดเตรียมการจัดการเธรดพูลที่เหมาะสำหรับแอ็พพลิเคชันขนาดใหญ่
การรวบรวมพร้อมกันทำให้การจัดการข้อมูลจำนวนมากง่ายขึ้นและสามารถลดความจำเป็นในการซิงโครไนซ์ได้อย่างมาก
ตัวแปรอะตอมมิกมีคุณสมบัติที่ลดการซิงโครไนซ์และช่วยหลีกเลี่ยงข้อผิดพลาดที่สอดคล้องกันของหน่วยความจำ
ThreadLocalRandom (ใน JDK 7) ให้การสร้างหมายเลข pseudorandom ที่มีประสิทธิภาพจากหลายเธรด
การแทนที่ที่ดีกว่าสำหรับการซิงโครไนซ์คือReentrantLockซึ่งใช้Lock
API
Reentrant mutual exclusion Lock ที่มีพฤติกรรมพื้นฐานและความหมายเหมือนกับการล็อกการมอนิเตอร์โดยนัยที่เข้าถึงได้โดยใช้วิธีการและข้อความที่ซิงโครไนซ์ แต่มีความสามารถเพิ่มเติม
ตัวอย่างที่มีล็อค:
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
อ้างถึงjava.util.concurrentและjava.util.concurrent.atomicแพ็คเกจเกินไปสำหรับการเขียนโปรแกรมการสร้างอื่น ๆ
อ้างถึงคำถามที่เกี่ยวข้องเช่นกัน:
วิธีการซิงโครไนซ์ใช้สำหรับล็อควัตถุทั้งหมดบล็อกแบบซิงโครไนซ์ใช้เพื่อล็อควัตถุเฉพาะ
โดยทั่วไปสิ่งเหล่านี้ส่วนใหญ่จะเหมือนกันนอกเหนือจากการมีความชัดเจนเกี่ยวกับการตรวจสอบของวัตถุที่ถูกนำมาใช้เทียบกับวัตถุนี้โดยนัย ข้อเสียข้อหนึ่งของวิธีการซิงโครไนซ์ที่ฉันคิดว่าบางครั้งถูกมองข้ามคือในการใช้การอ้างอิงแบบ "นี่" เพื่อซิงโครไนซ์กับคุณคุณกำลังเปิดความเป็นไปได้ของวัตถุภายนอก นั่นอาจเป็นข้อผิดพลาดที่ละเอียดอ่อนมากหากคุณพบเจอ การซิงโครไนซ์กับวัตถุที่ชัดเจนภายในหรือฟิลด์อื่นที่มีอยู่สามารถหลีกเลี่ยงปัญหานี้ได้
อย่างที่ได้กล่าวไปแล้วว่าบล็อกซิงโครไนซ์ที่นี่สามารถใช้ตัวแปรที่ผู้ใช้กำหนดเป็นวัตถุล็อคเมื่อฟังก์ชันซิงโครไนซ์ใช้เฉพาะ "นี่" และแน่นอนคุณสามารถจัดการกับส่วนต่าง ๆ ของฟังก์ชั่นของคุณที่ควรจะซิงโครไนซ์ แต่ทุกคนบอกว่าไม่มีความแตกต่างระหว่างฟังก์ชั่นการซิงโครไนซ์และบล็อกซึ่งครอบคลุมฟังก์ชั่นทั้งหมดโดยใช้ "นี่" เป็นวัตถุล็อค นั่นไม่เป็นความจริงความแตกต่างคือในรหัสไบต์ซึ่งจะถูกสร้างขึ้นในทั้งสองสถานการณ์ ในกรณีที่มีการใช้บล็อกแบบซิงโครไนซ์ควรได้รับการจัดสรรตัวแปรโลคัลซึ่งเก็บการอ้างอิงถึง "นี่" และเป็นผลให้เรามีขนาดใหญ่ขึ้นเล็กน้อยสำหรับฟังก์ชั่น (ไม่เกี่ยวข้องถ้าคุณมีฟังก์ชั่นจำนวนน้อยเท่านั้น)
คำอธิบายโดยละเอียดเพิ่มเติมเกี่ยวกับความแตกต่างที่คุณสามารถพบได้ที่นี่: http://www.artima.com/insidejvm/ed2/threadsynchP.html
ในกรณีที่วิธีการซิงโครไนซ์ล็อคจะได้รับบนวัตถุ แต่ถ้าคุณใช้ซิงโครไนซ์บล็อกคุณจะมีตัวเลือกในการระบุวัตถุที่จะได้รับการล็อค
ตัวอย่าง:
Class Example {
String test = "abc";
// lock will be acquired on String test object.
synchronized (test) {
// do something
}
lock will be acquired on Example Object
public synchronized void testMethod() {
// do some thing
}
}
ฉันรู้ว่านี่เป็นคำถามเก่า แต่ด้วยการอ่านคำตอบอย่างรวดเร็วของฉันที่นี่ฉันไม่เห็นใครพูดถึงว่าบางครั้งsynchronized
วิธีการอาจล็อคผิด
จาก Java Concurrency In ปฏิบัติ (หน้า 72):
public class ListHelper<E> {
public List<E> list = Collections.syncrhonizedList(new ArrayList<>());
...
public syncrhonized boolean putIfAbsent(E x) {
boolean absent = !list.contains(x);
if(absent) {
list.add(x);
}
return absent;
}
รหัสด้านบนมีลักษณะเป็นเธรดที่ปลอดภัย อย่างไรก็ตามในความเป็นจริงมันไม่ได้เป็น ในกรณีนี้การล็อคจะได้รับในอินสแตนซ์ของคลาส อย่างไรก็ตามเป็นไปได้ที่รายการจะถูกแก้ไขโดยเธรดอื่นที่ไม่ได้ใช้วิธีการนั้น แนวทางที่ถูกต้องน่าจะใช้
public boolean putIfAbsent(E x) {
synchronized(list) {
boolean absent = !list.contains(x);
if(absent) {
list.add(x);
}
return absent;
}
}
รหัสด้านบนจะบล็อกเธรดทั้งหมดที่พยายามแก้ไขรายการจากการแก้ไขรายการจนกว่าบล็อกที่ซิงโครไนซ์จะเสร็จสมบูรณ์
List
อาจนำไปสู่ปัญหาประสิทธิภาพการทำงานหากมีบันทึกของรหัสที่ไม่จำเป็นต้องมีการซิงโครไนซ์
ในทางปฏิบัติแล้วข้อดีของวิธีการซิงโครไนซ์กับบล็อกที่ซิงโครไนซ์คือมีความทนทานมากกว่าคนโง่ เนื่องจากคุณไม่สามารถเลือกวัตถุที่ต้องการล็อคคุณไม่สามารถใช้ไวยากรณ์ของวิธีการซิงโครไนซ์ในทางที่ผิดเพื่อทำสิ่งที่โง่เช่นการล็อคสตริงตัวอักษรหรือการล็อคเนื้อหาของฟิลด์ที่ไม่แน่นอนซึ่งเปลี่ยนจากหัวข้อ
ในอีกทางหนึ่งด้วยวิธีการซิงโครไนซ์คุณไม่สามารถป้องกันการล็อกจากการรับมาโดยเธรดใด ๆ ที่สามารถรับการอ้างอิงไปยังวัตถุ
ดังนั้นการใช้การซิงโครไนซ์เป็นตัวดัดแปลงวิธีการจะดีกว่าในการปกป้องวัวของคุณจากการทำร้ายตัวเองในขณะที่การใช้บล็อกที่ซิงโครไนซ์ร่วมกับวัตถุล็อคสุดท้ายส่วนตัวจะดีกว่าในการปกป้องรหัสของคุณเอง
จากสรุปข้อมูลจำเพาะ Java: http://www.cs.cornell.edu/andru/javaspec/17.doc.html
คำสั่งที่ทำข้อมูลให้ตรงกัน (§14.17) คำนวณการอ้างอิงไปยังวัตถุ จากนั้นจะพยายามทำการล็อคการทำงานกับวัตถุนั้นและจะไม่ดำเนินการต่อไปจนกว่าการดำเนินการล็อคจะเสร็จสมบูรณ์ ...
วิธีการซิงโครไนซ์ (§8.4.3.5) จะทำการล็อกโดยอัตโนมัติเมื่อมีการเรียกใช้ ร่างกายจะไม่ถูกดำเนินการจนกว่าการดำเนินการล็อคจะเสร็จสมบูรณ์ หากวิธีนั้นเป็นวิธีการแบบอินสแตนซ์มันจะล็อคการล็อคที่เกี่ยวข้องกับอินสแตนซ์ที่มันถูกเรียก (นั่นคือวัตถุที่จะเป็นที่รู้จักในเรื่องนี้ในระหว่างการดำเนินการของร่างกายของวิธีการ) หากวิธีการเป็นแบบคงที่มันล็อคล็อคที่เกี่ยวข้องกับวัตถุระดับที่แสดงถึงระดับที่มีการกำหนดวิธีการ ...
จากคำอธิบายเหล่านี้ฉันจะบอกว่าคำตอบก่อนหน้าส่วนใหญ่นั้นถูกต้องและวิธีการซิงโครไนซ์อาจมีประโยชน์อย่างยิ่งสำหรับวิธีแบบคงที่ซึ่งคุณจะต้องคิดหาวิธีการรับ "วัตถุคลาสที่แสดงถึงระดับที่วิธีนั้น ที่กำหนดไว้."
แก้ไข: ฉันเดิมคิดว่าสิ่งเหล่านี้เป็นคำพูดของสเป็ค Java ที่แท้จริง ชี้แจงว่าหน้านี้เป็นเพียงบทสรุป / คำอธิบายของข้อมูลจำเพาะ
TLDR; ค่าใช้synchronized
ปรับปรุงหรือsynchronized(this){...}
การแสดงออก แต่synchronized(myLock){...}
ที่myLock
เป็นเช่นสนามสุดท้ายถือเป็นวัตถุส่วนตัว
ความแตกต่างระหว่างการใช้โมดิsynchronized
ฟายเออร์ในการประกาศเมธอดและsynchronized(..){ }
นิพจน์ในส่วนของเมธอดคือ:
synchronized
ฟายเออร์ที่ระบุบนลายเซ็นของเมธอด
synchronized(this) { .... }
และthis
วัตถุเป็นล็อคเมื่อมีการประกาศในวิธีการไม่คงที่หรือชั้นล้อมรอบเมื่อมีการประกาศในวิธีการคงที่synchronized(...){...}
แสดงออกช่วยให้คุณ
อย่างไรก็ตามการใช้synchronized
ตัวดัดแปลงหรือsynchronized(...) {...}
ด้วยthis
วัตถุล็อค (ในsynchronized(this) {...}
) มีข้อเสียเหมือนกัน ทั้งคู่ใช้อินสแตนซ์ของตัวเองเป็นวัตถุล็อคเพื่อซิงโครไนซ์ นี้เป็นอันตรายเพราะไม่เพียง แต่วัตถุเอง แต่ใด ๆอื่น ๆ วัตถุภายนอก / รหัสที่มีการอ้างอิงไปยังวัตถุที่ยังสามารถใช้มันเป็นล็อคประสานกับผลข้างเคียงที่รุนแรงที่อาจเกิดขึ้น (ลดประสิทธิภาพการทำงานและการติดตาย )
ดังนั้นวิธีปฏิบัติที่ดีที่สุดคือไม่ใช้synchronized
ตัวดัดแปลงหรือsynchronized(...)
นิพจน์ร่วมกับthis
วัตถุล็อค แต่วัตถุล็อคส่วนตัวกับวัตถุนี้ ตัวอย่างเช่น:
public class MyService {
private final lock = new Object();
public void doThis() {
synchronized(lock) {
// do code that requires synchronous execution
}
}
public void doThat() {
synchronized(lock) {
// do code that requires synchronous execution
}
}
}
คุณยังสามารถใช้วัตถุล็อคหลาย ๆ อันได้ แต่ต้องมีการดูแลเป็นพิเศษเพื่อให้แน่ใจว่าสิ่งนี้จะไม่ส่งผลให้เกิดการหยุดชะงักเมื่อใช้งานซ้อนกัน
public class MyService {
private final lock1 = new Object();
private final lock2 = new Object();
public void doThis() {
synchronized(lock1) {
synchronized(lock2) {
// code here is guaranteed not to be executes at the same time
// as the synchronized code in doThat() and doMore().
}
}
public void doThat() {
synchronized(lock1) {
// code here is guaranteed not to be executes at the same time
// as the synchronized code in doThis().
// doMore() may execute concurrently
}
}
public void doMore() {
synchronized(lock2) {
// code here is guaranteed not to be executes at the same time
// as the synchronized code in doThis().
// doThat() may execute concurrently
}
}
}
ฉันคิดว่าคำถามนี้เป็นเรื่องเกี่ยวกับความแตกต่างระหว่างการเริ่มต้นThread Safe SingletonและLazy ด้วยการล็อคการตรวจสอบซ้ำ ฉันมักจะอ้างถึงบทความนี้เมื่อฉันจำเป็นต้องใช้ซิงเกิลที่เฉพาะเจาะจง
ทีนี้นี่คือเธรด Safe Singleton :
// Java program to create Thread Safe
// Singleton class
public class GFG
{
// private instance, so that it can be
// accessed by only by getInstance() method
private static GFG instance;
private GFG()
{
// private constructor
}
//synchronized method to control simultaneous access
synchronized public static GFG getInstance()
{
if (instance == null)
{
// if instance is null, initialize
instance = new GFG();
}
return instance;
}
}
ข้อดี:
การเริ่มต้น Lazy เป็นไปได้
มันเป็นหัวข้อที่ปลอดภัย
จุดด้อย:
- เมธอด getInstance () ถูกซิงโครไนซ์ดังนั้นจึงทำให้ประสิทธิภาพการทำงานช้าลงเนื่องจากหลายเธรดไม่สามารถเข้าถึงพร้อมกันได้
นี่คือการเริ่มต้น Lazy ด้วยการล็อคการตรวจสอบซ้ำ :
// Java code to explain double check locking
public class GFG
{
// private instance, so that it can be
// accessed by only by getInstance() method
private static GFG instance;
private GFG()
{
// private constructor
}
public static GFG getInstance()
{
if (instance == null)
{
//synchronized block to remove overhead
synchronized (GFG.class)
{
if(instance==null)
{
// if instance is null, initialize
instance = new GFG();
}
}
}
return instance;
}
}
ข้อดี:
การเริ่มต้น Lazy เป็นไปได้
นอกจากนี้ยังปลอดภัยด้าย
ประสิทธิภาพลดลงเนื่องจากคำหลักที่ซิงโครไนซ์ถูกเอาชนะ
จุดด้อย:
ครั้งแรกอาจส่งผลต่อประสิทธิภาพ
ในฐานะที่เป็นข้อเสีย ของวิธีการตรวจสอบล็อคสองครั้งสามารถรับน้ำหนักได้ดังนั้นจึงสามารถใช้สำหรับการใช้งานแบบมัลติเธรดประสิทธิภาพสูง
โปรดอ้างอิงบทความนี้สำหรับรายละเอียดเพิ่มเติม:
https://www.geeksforgeeks.org/java-singleton-design-pattern-practices-examples/
การซิงโครไนซ์กับเธรด 1) ไม่ใช้ซิงโครไนซ์ (นี่) ในเธรดไม่ทำงาน การซิงโครไนซ์กับ (นี้) ใช้เธรดปัจจุบันเป็นวัตถุเธรดการล็อก เนื่องจากแต่ละเธรดเป็นอิสระจากเธรดอื่น ๆ จึงไม่มีการประสานงานการซิงโครไนซ์ 2) การทดสอบโค้ดแสดงให้เห็นว่าใน Java 1.6 บน Mac การซิงโครไนซ์เมธอดไม่ทำงาน 3) ทำข้อมูลให้ตรงกัน (lockObj) โดยที่ lockObj เป็นวัตถุที่ใช้ร่วมกันทั่วไปของกระทู้ทั้งหมดที่ทำข้อมูลให้ตรงกันจะทำงาน 4) การทำงานของ ReenterantLock.lock () และ. unlock () ดูบทเรียนของจาวาสำหรับสิ่งนี้
รหัสต่อไปนี้แสดงให้เห็นถึงจุดเหล่านี้ นอกจากนี้ยังมี Vector-safe แบบเธรดซึ่งจะถูกแทนที่สำหรับ ArrayList เพื่อแสดงว่าเธรดจำนวนมากที่เพิ่มไปยัง Vector จะไม่สูญเสียข้อมูลใด ๆ ในขณะที่ ArrayList เดียวกันจะสูญเสียข้อมูล 0) รหัสปัจจุบันแสดงการสูญเสียข้อมูลเนื่องจากสภาพการแข่งขัน A) แสดงความคิดเห็นบรรทัดปัจจุบันที่มีข้อความกำกับและยกเลิกการใส่เครื่องหมายบรรทัด A ข้างบนแล้วเรียกใช้เมธอดจะสูญเสียข้อมูล แต่ไม่ควร B) ย้อนกลับขั้นตอน A ยกเลิกการใส่ข้อคิดเห็น B และ // end block} จากนั้นเรียกใช้เพื่อดูผลลัพธ์ที่ไม่มีการสูญเสียข้อมูล C) แสดงความคิดเห็น B, ไม่ใส่ข้อคิดเห็น C รันดูการซิงโครไนซ์ (นี้) สูญเสียข้อมูลตามที่คาดไว้ ไม่มีเวลาที่จะเปลี่ยนแปลงรูปแบบทั้งหมดหวังว่านี่จะช่วยได้ หากการซิงโครไนซ์ (นี้) หรือการซิงโครไนซ์วิธีการทำงานโปรดระบุเวอร์ชันของ Java และ OS ที่คุณทดสอบ ขอบคุณ.
import java.util.*;
/** RaceCondition - Shows that when multiple threads compete for resources
thread one may grab the resource expecting to update a particular
area but is removed from the CPU before finishing. Thread one still
points to that resource. Then thread two grabs that resource and
completes the update. Then thread one gets to complete the update,
which over writes thread two's work.
DEMO: 1) Run as is - see missing counts from race condition, Run severa times, values change
2) Uncomment "synchronized(countLock){ }" - see counts work
Synchronized creates a lock on that block of code, no other threads can
execute code within a block that another thread has a lock.
3) Comment ArrayList, unComment Vector - See no loss in collection
Vectors work like ArrayList, but Vectors are "Thread Safe"
May use this code as long as attribution to the author remains intact.
/mf
*/
public class RaceCondition {
private ArrayList<Integer> raceList = new ArrayList<Integer>(); // simple add(#)
// private Vector<Integer> raceList = new Vector<Integer>(); // simple add(#)
private String countLock="lock"; // Object use for locking the raceCount
private int raceCount = 0; // simple add 1 to this counter
private int MAX = 10000; // Do this 10,000 times
private int NUM_THREADS = 100; // Create 100 threads
public static void main(String [] args) {
new RaceCondition();
}
public RaceCondition() {
ArrayList<Thread> arT = new ArrayList<Thread>();
// Create thread objects, add them to an array list
for( int i=0; i<NUM_THREADS; i++){
Thread rt = new RaceThread( ); // i );
arT.add( rt );
}
// Start all object at once.
for( Thread rt : arT ){
rt.start();
}
// Wait for all threads to finish before we can print totals created by threads
for( int i=0; i<NUM_THREADS; i++){
try { arT.get(i).join(); }
catch( InterruptedException ie ) { System.out.println("Interrupted thread "+i); }
}
// All threads finished, print the summary information.
// (Try to print this informaiton without the join loop above)
System.out.printf("\nRace condition, should have %,d. Really have %,d in array, and count of %,d.\n",
MAX*NUM_THREADS, raceList.size(), raceCount );
System.out.printf("Array lost %,d. Count lost %,d\n",
MAX*NUM_THREADS-raceList.size(), MAX*NUM_THREADS-raceCount );
} // end RaceCondition constructor
class RaceThread extends Thread {
public void run() {
for ( int i=0; i<MAX; i++){
try {
update( i );
} // These catches show when one thread steps on another's values
catch( ArrayIndexOutOfBoundsException ai ){ System.out.print("A"); }
catch( OutOfMemoryError oome ) { System.out.print("O"); }
}
}
// so we don't lose counts, need to synchronize on some object, not primitive
// Created "countLock" to show how this can work.
// Comment out the synchronized and ending {, see that we lose counts.
// public synchronized void update(int i){ // use A
public void update(int i){ // remove this when adding A
// synchronized(countLock){ // or B
// synchronized(this){ // or C
raceCount = raceCount + 1;
raceList.add( i ); // use Vector
// } // end block for B or C
} // end update
} // end RaceThread inner class
} // end RaceCondition outter class