เหตุใดชั้นนี้จึงไม่ปลอดภัย


95
class ThreadSafeClass extends Thread
{
     private static int count = 0;

     public synchronized static void increment()
     {
         count++;
     }

     public synchronized void decrement()
     {
         count--;
     }
}

ใครช่วยอธิบายได้ไหมว่าทำไมชั้นเรียนข้างบนจึงไม่ปลอดภัย


6
ฉันไม่รู้เกี่ยวกับ Java แต่ดูเหมือนว่าแต่ละวิธีเหล่านั้นจะปลอดภัยต่อเธรดแต่ละรายการแต่คุณสามารถมีเธรดในแต่ละวิธีพร้อมกันได้ บางทีถ้าคุณมีวิธีเดียวที่ใช้บูล ( increment) มันจะปลอดภัยสำหรับเธรด หรือถ้าคุณใช้วัตถุล็อค อย่างที่บอกว่าฉันไม่รู้เกี่ยวกับ Java - ความคิดเห็นของฉันเกิดจากความรู้ C #
Wai Ha Lee


ฉันไม่รู้จัก Javavery ดี แต่เพื่อซิงโครไนซ์การเข้าถึงตัวแปรคงที่synchronizedควรใช้ในวิธีการแบบคงที่เท่านั้น ดังนั้นในความคิดของฉันแม้ว่าคุณจะลบincrementเมธอด แต่ก็ยังไม่ปลอดภัยเธรดเนื่องจากสองอินสแตนซ์ (ซึ่งมีการซิงโครไนซ์การเข้าถึงผ่านอินสแตนซ์เดียวกันเท่านั้น) สามารถเรียกใช้เมธอดพร้อมกันได้
Onur

4
เป็นเธรดที่ปลอดภัยตราบเท่าที่คุณไม่เคยสร้างอินสแตนซ์ของคลาส
Benjamin Gruenbaum

1
ทำไมคุณถึงคิดว่ามันไม่ปลอดภัย
Raedwald

คำตอบ:


134

เนื่องจากincrementวิธีนี้staticจะซิงโครไนซ์กับวัตถุคลาสสำหรับThreadSafeClass. decrementวิธีการไม่ได้เป็นแบบคงที่และจะประสานอินสแตนซ์ที่ใช้ในการเรียกมันว่า กล่าวคือพวกเขาจะซิงโครไนซ์กับวัตถุที่แตกต่างกันดังนั้นเธรดที่แตกต่างกันสองเธรดจึงสามารถดำเนินการเมธอดได้ในเวลาเดียวกัน เนื่องจากการดำเนินการ++และ--ไม่ใช่อะตอมคลาสจึงไม่ปลอดภัยต่อเธรด

นอกจากนี้ตั้งแต่countเป็นstaticการปรับเปลี่ยนได้จากdecrementซึ่งเป็นข้อมูลให้ตรงกันเช่นวิธีการจะไม่ปลอดภัยเพราะมันสามารถถูกเรียกบนอินสแตนซ์ที่แตกต่างกันและปรับเปลี่ยนcountที่เห็นพ้องกันว่าวิธีการ


12
คุณอาจเพิ่มเนื่องจากcountมีที่staticมีวิธีการเช่นdecrement()เป็นความผิดแม้ว่าไม่มีstatic increment()วิธีการเป็นสองหัวข้อสามารถเรียกใช้decrement()ในกรณีที่แตกต่างกันการปรับเปลี่ยนเคาน์เตอร์เดียวกันพร้อมกัน
Holger

1
นั่นอาจจะเป็นเหตุผลที่ดีที่ชอบใช้synchronizedบล็อกในทั่วไป (แม้ในเนื้อหาวิธีการทั้งหมด) แทนการใช้เครื่องปรับวิธีคือและsynchronized(this) { ... } synchronized(ThreadSafeClass.class) { ... }
Bruno

++และ--volatile intไม่ได้เป็นอะตอมแม้ใน Synchronizedดูแลปัญหาการอ่าน / อัปเดต / เขียนด้วย++/ --แต่staticคีย์เวิร์ดคือคีย์ที่นี่ คำตอบที่ดี!
Chris Cirefice

เรื่อง, การปรับเปลี่ยน [สนามคง] จาก ... อินสแตนซ์ตรงกันวิธีการที่ไม่ถูกต้อง : โดยเนื้อแท้มีอะไรผิดปกติกับการเข้าถึงตัวแปรคงที่จากภายในวิธีการเช่นเป็นและมีอะไรผิดปกติกับเนื้อแท้เข้าถึงจากsynchronizedวิธีการเช่นใดอย่างหนึ่ง อย่าคาดหวังว่า "ซิงโครไนซ์" ในวิธีการอินสแตนซ์จะให้การป้องกันข้อมูลคงที่ ปัญหาเดียวคือสิ่งที่คุณพูดในย่อหน้าแรกของคุณ: วิธีการนี้ใช้การล็อกที่แตกต่างกันเพื่อพยายามปกป้องข้อมูลเดียวกันและแน่นอนว่าไม่มีการป้องกันใด ๆ เลย
Solomon Slow

23

คุณมีวิธีการซิงโครไนซ์สองวิธี แต่วิธีหนึ่งเป็นแบบคงที่และอีกวิธีหนึ่งไม่ใช่ เมื่อเข้าถึงวิธีการซิงโครไนซ์ขึ้นอยู่กับประเภทของมัน (คงที่หรือไม่คงที่) วัตถุอื่นจะถูกล็อค สำหรับวิธีการแบบคงที่การล็อกจะถูกวางไว้บนคลาสอ็อบเจ็กต์ในขณะที่สำหรับบล็อกที่ไม่คงที่นั้นจะมีการล็อคอินสแตนซ์ของคลาสที่รันเมธอด เนื่องจากคุณมีอ็อบเจ็กต์ที่ล็อกสองอันที่แตกต่างกันคุณจึงสามารถมีเธรดสองเธรดที่แก้ไขอ็อบเจ็กต์เดียวกันพร้อมกันได้


14

ใครช่วยอธิบายได้ไหมว่าทำไมชั้นเรียนข้างบนจึงไม่ปลอดภัย

  • increment เป็นแบบคงที่การซิงโครไนซ์จะทำในคลาสเอง
  • decrementการไม่คงที่การซิงโครไนซ์จะทำในอินสแตนซ์ของอ็อบเจ็กต์ แต่ไม่ได้รักษาความปลอดภัยอะไรcountเลย

ฉันต้องการเพิ่มสิ่งนั้นเพื่อประกาศตัวนับเธรดที่ปลอดภัยฉันเชื่อว่าวิธีที่ง่ายที่สุดคือการใช้AtomicIntegerแทน int ดั้งเดิม

ให้ฉันเปลี่ยนเส้นทางคุณไปยังjava.util.concurrent.atomicข้อมูลแพ็คเกจ


7

คำตอบของคนอื่นอธิบายเหตุผลได้ดีทีเดียว ฉันเพิ่งเพิ่มบางสิ่งเพื่อสรุปsynchronized:

public class A {
    public synchronized void fun1() {}

    public synchronized void fun2() {}

    public void fun3() {}

    public static synchronized void fun4() {}

    public static void fun5() {}
}

A a1 = new A();

synchronizedเปิดfun1และfun2ซิงโครไนซ์ในระดับวัตถุอินสแตนซ์ synchronizedon fun4ถูกซิงโครไนซ์ในระดับอ็อบเจ็กต์คลาส ซึ่งหมายความว่า:

  1. เมื่อ 2 เธรดโทรa1.fun1()ในเวลาเดียวกันการโทรครั้งหลังจะถูกบล็อก
  2. เมื่อการโทรเธรด 1 a1.fun1()และเธรด 2 โทรa1.fun2()ในเวลาเดียวกันการโทรครั้งหลังจะถูกบล็อก
  3. เมื่อการโทรเธรด 1 a1.fun1()และเธรด 2 โทรa1.fun3()ในเวลาเดียวกันจะไม่มีการบล็อก 2 วิธีจะถูกดำเนินการในเวลาเดียวกัน
  4. เมื่อเธรด 1 โทรA.fun4()หากเธรดอื่นเรียกA.fun4()หรือA.fun5()ในเวลาเดียวกันการโทรครั้งหลังจะถูกบล็อกเนื่องจากsynchronizedออนfun4คือระดับคลาส
  5. เมื่อเธรด 1 โทรA.fun4(), เธรด 2 โทรa1.fun1()ในเวลาเดียวกัน, ไม่มีการบล็อก, 2 วิธีการจะถูกดำเนินการในเวลาเดียวกัน

6
  1. decrementกำลังล็อคสิ่งที่แตกต่างกันincrementเพื่อไม่ให้กันและกันจากการทำงาน
  2. การเรียกdecrementใช้อินสแตนซ์หนึ่งกำลังล็อกสิ่งที่แตกต่างกันเพื่อเรียกdecrementใช้อินสแตนซ์อื่น แต่การเรียกใช้อินสแตนซ์หนึ่งจะส่งผลกระทบต่อสิ่งเดียวกัน

ประการแรกหมายถึงการเรียกทับซ้อนกันincrementและdecrementอาจส่งผลให้เกิดการยกเลิก (ถูกต้อง) เพิ่มขึ้นหรือลดลง

ประการที่สองหมายความว่าการเรียกซ้อนกันสองครั้งไปยังdecrementอินสแตนซ์ที่แตกต่างกันอาจส่งผลให้เกิดการลดลงสองครั้ง (ถูกต้อง) หรือการลดลงเพียงครั้งเดียว


4

เนื่องจากสองวิธีที่แตกต่างกันหนึ่งคือระดับอินสแตนซ์และอื่น ๆ คือระดับคลาสดังนั้นคุณต้องล็อควัตถุที่แตกต่างกัน 2 รายการเพื่อให้เป็น ThreadSafe


1

ตามที่อธิบายไว้ในคำตอบอื่น ๆ รหัสของคุณไม่ปลอดภัยต่อเธรดเนื่องจากวิธีการแบบคงที่จะincrement()ล็อกการตรวจสอบคลาสและวิธีการที่ไม่คงที่จะdecrement()ล็อกการตรวจสอบวัตถุ

สำหรับตัวอย่างโค้ดนี้มีทางออกที่ดีกว่าโดยไม่ต้องsynchronzedใช้คีย์เวิร์ด คุณต้องใช้AtomicInteger เพื่อให้เกิดความปลอดภัยของเธรด

เธรดปลอดภัยโดยใช้AtomicInteger:

import java.util.concurrent.atomic.AtomicInteger;

class ThreadSafeClass extends Thread {

    private static AtomicInteger count = new AtomicInteger(0);

    public static void increment() {
        count.incrementAndGet();
    }

    public static void decrement() {
        count.decrementAndGet();
    }

    public static int value() {
        return count.get();
    }

}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.