ปัญหาการเห็นพ้องบ่อยที่สุดที่คุณพบใน Java คืออะไร [ปิด]


192

นี่เป็นแบบสำรวจความคิดเห็นเกี่ยวกับปัญหาการเกิดพร้อมกันทั่วไปใน Java ตัวอย่างอาจเป็น deadlock แบบคลาสสิกหรือสภาพการแข่งขันหรือบางที EDT เธรดเธรดใน Swing ฉันสนใจทั้งปัญหาที่เป็นไปได้กว้าง แต่รวมถึงประเด็นที่พบบ่อยที่สุดด้วย ดังนั้นโปรดทิ้งคำตอบเฉพาะไว้สำหรับข้อบกพร่องของการทำงานพร้อมกันของ Java ต่อความคิดเห็นและโหวตถ้าคุณเห็นคำตอบที่คุณพบ


16
ทำไมสิ่งนี้จึงปิด สิ่งนี้มีประโยชน์ทั้งสำหรับโปรแกรมเมอร์คนอื่น ๆ ที่ขอร้องให้ทำงานพร้อมกันใน Java และมีความคิดว่าคลาสใดที่มีข้อบกพร่องที่เกิดขึ้นพร้อมกัน
L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳

@ Longpoke ข้อความปิดอธิบายถึงสาเหตุที่ปิด นี่ไม่ใช่คำถามที่มีคำตอบ "ถูกต้อง" เฉพาะเจาะจงมันเป็นคำถามแบบสำรวจความคิดเห็น / รายการเพิ่มเติม และ Stack Overflow ไม่ต้องการโฮสต์คำถามประเภทนี้ ถ้าคุณไม่เห็นด้วยกับนโยบายที่คุณอาจต้องการที่จะหารือเกี่ยวกับมันในเมตา
Andrzej Doyle

7
ฉันเดาว่าชุมชนไม่เห็นด้วยเนื่องจากบทความนี้มีผู้ชมมากกว่า 100 ครั้ง / วัน! ฉันได้พบมันมีประโยชน์มากเป็นฉันที่เกี่ยวข้องกับการพัฒนาเครื่องมือในการวิเคราะห์แบบคงที่ออกแบบมาโดยเฉพาะเพื่อแก้ไขปัญหาการทำงานพร้อมกันcontemplateltd.com/threadsafe การมีธนาคารที่มีปัญหาที่เกิดขึ้นพร้อมกันโดยทั่วไปนั้นยอดเยี่ยมสำหรับการทดสอบและปรับปรุง ThreadSafe
Craig Manson

รายการตรวจสอบการตรวจสอบโค้ดสำหรับ Java Concurrencyส่วนย่อยข้อผิดพลาดส่วนใหญ่ที่กล่าวถึงในคำตอบของคำถามนี้ในรูปแบบที่สะดวกสำหรับการตรวจสอบโค้ดแบบวันต่อวัน
leventov

คำตอบ:


125

ปัญหาที่เกิดขึ้นพร้อมกันที่พบบ่อยที่สุดที่ฉันเคยเห็นคือไม่ได้ตระหนักว่าเขตข้อมูลที่เขียนโดยหนึ่งเธรดไม่รับประกันว่าจะเห็นได้โดยเธรดอื่น แอปพลิเคชันทั่วไปของสิ่งนี้:

class MyThread extends Thread {
  private boolean stop = false;

  public void run() {
    while(!stop) {
      doSomeWork();
    }
  }

  public void setStop() {
    this.stop = true;
  }
}

ตราบใดที่หยุดไม่ระเหยหรือsetStopและrunจะไม่ตรงกันนี้ไม่รับประกันว่าจะทำงาน ความผิดพลาดนี้เป็นเรื่องที่โหดร้ายโดยเฉพาะอย่างยิ่งใน 99.999% มันจะไม่สำคัญในทางปฏิบัติเนื่องจากเธรดผู้อ่านจะเห็นการเปลี่ยนแปลงในที่สุด - แต่เราไม่รู้ว่าเขาจะเห็นได้เร็วแค่ไหน


9
ทางออกที่ดีสำหรับสิ่งนี้คือการทำให้ตัวแปรอินสแตนซ์หยุดเป็น AtomicBoolean มันแก้ปัญหาทั้งหมดของการไม่ลบเลือนในขณะที่ป้องกันคุณจากปัญหา JMM
Kirk Wylie

39
มันแย่กว่า 'เป็นเวลาหลายนาที' - คุณอาจไม่เคยเห็นมัน ภายใต้รูปแบบหน่วยความจำ JVM ได้รับอนุญาตให้เพิ่มประสิทธิภาพขณะที่ (! หยุด) เข้าสู่ในขณะที่ (จริง) จากนั้นคุณจะถูกซ่อน สิ่งนี้อาจเกิดขึ้นกับ VMs บางตัวเท่านั้นในโหมดเซิร์ฟเวอร์เฉพาะเมื่อ JVM คอมไพล์ใหม่หลังจากการวนซ้ำ x ของลูปเป็นต้น Ouch!
Cowan

2
ทำไมคุณถึงต้องการใช้ AtomicBoolean เหนือบูลีนที่ระเหยง่าย? ฉันกำลังพัฒนาสำหรับรุ่น 1.4+ ดังนั้นจะมีข้อผิดพลาดใด ๆ ที่เพิ่งประกาศความผันผวนหรือไม่
สระว่ายน้ำ

2
นิคฉันคิดว่าเป็นเพราะอะตอม CAS มักจะเร็วกว่าความผันผวน หากคุณกำลังพัฒนาตัวเลือกที่ปลอดภัยเพียงตัวเดียวของคุณ IMHO คือการใช้การซิงโครไนซ์ที่ระเหยได้ใน 1.4 ไม่ได้มีการรับประกันหน่วยความจำที่แข็งแกร่งตามที่มีอยู่ใน Java 5
Kutzi

5
@Thomas: นั่นเป็นเพราะโมเดลหน่วยความจำ Java คุณควรอ่านเกี่ยวกับเรื่องนี้หากคุณต้องการทราบรายละเอียด (Java Concurrency ในการปฏิบัติโดย Brian Goetz อธิบายได้ดีเช่น) ในระยะสั้น: ถ้าคุณใช้คำหลักประสานหน่วยความจำ / โครงสร้าง (เช่นระเหยตรงกัน AtomicXyz แต่ยังเมื่อกระทู้เสร็จสิ้น) หนึ่งในกระทู้มีการรับประกันใด ๆ เพื่อดูการเปลี่ยนแปลงที่เกิดขึ้นกับข้อมูลใด ๆ ที่กระทำโดยหัวข้อที่แตกต่าง
Kutzi

179

ปัญหาการทำงานพร้อมกันที่เจ็บปวดที่สุดอันดับ 1ของฉันเคยเกิดขึ้นเมื่อห้องสมุดโอเพ่นซอร์สสองแห่งที่ต่างกันทำสิ่งนี้:

private static final String LOCK = "LOCK";  // use matching strings 
                                            // in two different libraries

public doSomestuff() {
   synchronized(LOCK) {
       this.work();
   }
}

ในภาพรวมก่อนหน้านี้ดูเหมือนว่าจะเป็นตัวอย่างของการซิงโครไนซ์เล็กน้อย อย่างไรก็ตาม; เนื่องจากสตริงมีการฝึกงานใน Java สตริงตัวอักษร"LOCK"จะกลายเป็นอินสแตนซ์เดียวกันของjava.lang.String(แม้ว่าพวกเขาจะประกาศอย่างสมบูรณ์โดยสิ้นเชิงจากกัน) ผลลัพธ์ที่ได้ก็ไม่ดีอย่างเห็นได้ชัด


63
นี่คือหนึ่งในเหตุผลที่ฉันชอบ Object LOCK สุดท้ายคงส่วนตัว = วัตถุใหม่ ();
Andrzej Doyle

17
ฉันรักมัน - โอ้นี้เป็นที่น่ารังเกียจ :)
Thorbjørn Ravn Andersen

7
นั่นเป็นสิ่งที่ดีสำหรับ Java Puzzlers 2
Dov Wasserman

12
จริงๆแล้ว ... มันทำให้ฉันอยากให้คอมไพเลอร์ปฏิเสธที่จะอนุญาตให้คุณซิงโครไนซ์กับ String รับสายอักขระการฝึกงานไม่มีกรณีที่จะเป็น "สิ่งที่ดี (tm)"
Jared

3
@ Jared: "จนกว่าสตริงจะฝึกงาน" ไม่เหมาะสม เงื่อนไขไม่น่าอัศจรรย์ "กลายเป็น" ฝึกงาน String.intern () ส่งคืนวัตถุอื่นยกเว้นว่าคุณมีอินสแตนซ์ที่ยอมรับได้ของสตริงที่ระบุ นอกจากนี้สตริงตัวอักษรและนิพจน์ค่าคงที่ค่าสตริงทั้งหมดจะถูกฝึกงาน เสมอ. ดูเอกสารสำหรับ String.intern () และ§3.10.5ของ JLS
ลอเรนซ์ Gonsalves

65

ปัญหาคลาสสิกอย่างหนึ่งคือการเปลี่ยนวัตถุที่คุณซิงโครไนซ์ขณะซิงโครไนซ์มัน

synchronized(foo) {
  foo = ...
}

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


19
มีการตรวจสอบ IDEA สำหรับสิ่งนี้เรียกว่า "การซิงโครไนซ์บนเขตข้อมูลที่ไม่ใช่ครั้งสุดท้ายไม่น่าจะมีความหมายที่เป็นประโยชน์" ดีมาก.
Jen S.

8
ฮ่า ... ตอนนี้มันเป็นคำอธิบายที่ทรมาน "ไม่น่าจะมีความหมายที่เป็นประโยชน์" อาจอธิบายได้ว่าเป็น :)
Alex Miller

ฉันคิดว่ามันเป็น Bitter Java ที่มีอยู่ใน ReadWriteLock โชคดีที่ตอนนี้เรามี java.util.concurrency.locks และดั๊กก็เพิ่มขึ้นอีกเล็กน้อย
Tom Hawtin - tackline

ฉันเคยเห็นปัญหานี้บ่อยครั้งเช่นกัน ซิงค์กับวัตถุสุดท้ายเท่านั้นสำหรับเรื่องนั้น FindBugs และคณะ ช่วยด้วย
gimpf

นี่เป็นเพียงปัญหาระหว่างการมอบหมาย (ดูตัวอย่างของ @Alex Miller ด้านล่างพร้อมแผนที่) ตัวอย่างแผนที่นั้นมีปัญหาเดียวกันนี้ด้วยหรือไม่
Alex Beardsley

50

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


คุณรู้จักโปรเจ็กต์โอเพนซอร์สที่มีบั๊กนี้ในบางเวอร์ชั่นหรือไม่? ฉันกำลังมองหาตัวอย่างที่เป็นรูปธรรมของข้อผิดพลาดนี้ในซอฟต์แวร์โลกแห่งความจริง
reprogrammer

48

ไม่ซิงโครไนซ์กับวัตถุที่ส่งคืนโดยCollections.synchronizedXXX()เฉพาะอย่างยิ่งในระหว่างการทำซ้ำหรือการดำเนินการหลายอย่าง:

Map<String, String> map = Collections.synchronizedMap(new HashMap<String, String>());

...

if(!map.containsKey("foo"))
    map.put("foo", "bar");

นั่นคือที่ไม่ถูกต้อง แม้จะมีการดำเนินการเดี่ยวsynchronizedสถานะของแผนที่ระหว่างการเรียกใช้containsและputสามารถเปลี่ยนแปลงได้โดยเธรดอื่น มันควรจะเป็น:

synchronized(map) {
    if(!map.containsKey("foo"))
        map.put("foo", "bar");
}

หรือด้วยการConcurrentMapใช้งาน:

map.putIfAbsent("foo", "bar");

6
หรือดีกว่าให้ใช้ ConcurrentHashMap และใส่ IfAbsent
Tom Hawtin - tackline

47

ล็อคตรวจสอบอีกครั้ง โดยและขนาดใหญ่

กระบวนทัศน์ที่ฉันเริ่มเรียนรู้ปัญหาเมื่อฉันทำงานที่ BEA คือผู้คนจะตรวจสอบซิงเกิลในวิธีต่อไปนี้:

public Class MySingleton {
  private static MySingleton s_instance;
  public static MySingleton getInstance() {
    if(s_instance == null) {
      synchronized(MySingleton.class) { s_instance = new MySingleton(); }
    }
    return s_instance;
  }
}

สิ่งนี้ไม่ได้ผลเนื่องจากเธรดอื่นอาจเข้าสู่บล็อกที่ซิงโครไนซ์และ s_instance จะไม่เป็นโมฆะอีกต่อไป ดังนั้นการเปลี่ยนแปลงตามธรรมชาติคือการทำให้:

  public static MySingleton getInstance() {
    if(s_instance == null) {
      synchronized(MySingleton.class) {
        if(s_instance == null) s_instance = new MySingleton();
      }
    }
    return s_instance;
  }

ไม่สามารถใช้งานได้เนื่องจาก Java Memory Model ไม่รองรับ คุณต้องประกาศ s_instance ว่ามีความผันผวนเพื่อให้มันทำงานได้และแม้กระทั่งมันจะทำงานบน Java 5 เท่านั้น

คนที่ไม่คุ้นเคยกับความซับซ้อนของ Java หน่วยความจำรุ่นระเบียบนี้ขึ้นตลอดเวลา


7
รูปแบบ enum ซิงเกิลแก้ไขปัญหาเหล่านี้ทั้งหมด (ดูความคิดเห็นของ Josh Bloch เกี่ยวกับเรื่องนี้) ความรู้เกี่ยวกับการดำรงอยู่ของมันควรจะแพร่หลายมากขึ้นในหมู่โปรแกรมเมอร์ Java
โรบิน

ฉันยังไม่ได้วิ่งข้ามกรณีเดียวที่การกำหนดค่าเริ่มต้นที่ขี้เกียจของซิงเกิลนั้นเหมาะสมจริงๆ และถ้ามันเป็นเพียงประกาศวิธีการทำข้อมูลให้ตรงกัน
Dov Wasserman

3
นี่คือสิ่งที่ฉันใช้สำหรับการเริ่มต้น Lazy ของชั้นเรียน Singleton และไม่จำเป็นต้องมีการซิงโครไนซ์เนื่องจากรับประกันโดย java โดยปริยาย class Foo {ตัวยึดคลาสคงที่ {static Foo foo = new Foo (); } static Foo getInstance () {return Holder.foo; }}
Irfan Zulfiqar

Irfan เรียกว่าวิธีการของพัคห์วจากสิ่งที่ฉันจำได้
Chris R

@Robin มันง่ายกว่าไหมถ้าจะใช้ initializer คงที่? สิ่งเหล่านี้จะรับประกันว่าจะเรียกใช้ซิงโครไนซ์เสมอ
matt b

37

แม้ว่าอาจจะไม่ใช่สิ่งที่คุณขออย่างแน่นอนปัญหาที่เกี่ยวข้องกับการเกิดพร้อมกันบ่อยที่สุดที่ฉันเคยพบ

java.util.ConcurrentModificationException

เกิดจากสิ่งที่ชอบ:

List<String> list = new ArrayList<String>(Arrays.asList("a", "b", "c"));
for (String string : list) { list.remove(string); }

ไม่นั่นคือสิ่งที่ฉันกำลังมองหา ขอบคุณ!
Alex Miller

30

เป็นเรื่องง่ายที่จะคิดว่าคอลเลกชันที่ซิงโครไนซ์จะให้การป้องกันแก่คุณมากกว่าที่เป็นจริงและลืมล็อคระหว่างการโทร ฉันเห็นข้อผิดพลาดนี้สองสามครั้ง:

 List<String> l = Collections.synchronizedList(new ArrayList<String>());
 String[] s = l.toArray(new String[l.size()]);

ตัวอย่างเช่นในบรรทัดที่สองด้านบนtoArray()และsize()วิธีการต่างก็ปลอดภัยทั้งเธรดด้วยตนเอง แต่size()ถูกประเมินแยกต่างหากจากtoArray()และล็อคในรายการไม่ได้ถูกพักไว้ระหว่างการโทรทั้งสองนี้

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


5
ตัวอย่างที่ดี ฉันคิดว่าฉันจะชอล์กนี้โดยทั่วไปว่า "องค์ประกอบของการปฏิบัติการปรมาณูไม่ใช่อะตอม" (ดูข้อมูลระเหย ++ สำหรับตัวอย่างง่ายๆอื่น)
อเล็กซ์มิลเลอร์

29

ข้อผิดพลาดที่พบบ่อยที่สุดที่เราเห็นว่าฉันทำงานคือโปรแกรมเมอร์ทำงานเป็นเวลานานเช่นการโทรเซิร์ฟเวอร์บน EDT ล็อค GUI ไว้สักครู่และทำให้แอปไม่ตอบสนอง


หนึ่งในคำตอบเหล่านั้นฉันหวังว่าฉันจะให้มากกว่าหนึ่งจุดสำหรับ
Epaga

2
EDT = เธรดการส่งเหตุการณ์
mjj1409

28

ลืมที่จะรอ () (หรือ Condition.await ()) ในลูปตรวจสอบว่าเงื่อนไขการรอเป็นจริงจริง หากปราศจากสิ่งนี้คุณจะพบข้อบกพร่องจากการรอคอย () การปลอมแปลง การใช้ Canonical ควรเป็น:

 synchronized (obj) {
     while (<condition does not hold>) {
         obj.wait();
     }
     // do stuff based on condition being true
 }

26

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


ใช่และมีเครื่องมือที่ดีในการจัดการกับตัวจัดการตอนนี้
Alex Miller

3
คุณสามารถโพสต์ลิงก์ไปยังบทความหรือข้อมูลอ้างอิงใด ๆ ที่อธิบายรายละเอียดได้มากขึ้นหรือไม่
Abhijeet Kashnia

22

จนกว่าฉันจะเข้าชั้นเรียนกับ Brian Goetz ฉันไม่ได้ตระหนักว่าการไม่ซิงโครไนซ์getterของฟิลด์ส่วนตัวที่setterถูกซิงโครไนซ์ผ่านการซิงโครไนซ์จะไม่รับประกันว่าจะส่งคืนค่าที่อัปเดต เฉพาะเมื่อตัวแปรได้รับการปกป้องโดยบล็อกที่ซิงโครไนซ์ทั้งในการอ่านและเขียนคุณจะได้รับการรับประกันถึงค่าล่าสุดของตัวแปร

public class SomeClass{
    private Integer thing = 1;

    public synchronized void setThing(Integer thing)
        this.thing = thing;
    }

    /**
     * This may return 1 forever and ever no matter what is set
     * because the read is not synched
     */
    public Integer getThing(){
        return thing;  
    }
}

5
ในภายหลังของ JVM (1.5 และข้างหน้าฉันคิดว่า) การใช้สารระเหยจะแก้ไขได้เช่นกัน
James Schek

2
ไม่จำเป็น. สารระเหยช่วยให้คุณมีค่าล่าสุดดังนั้นจึงป้องกันการกลับมาเป็น 1 ตลอดไป แต่มันไม่ได้ให้การล็อค มันใกล้ แต่ก็ไม่เหมือนกัน
John Russell

1
@JohnRussell ฉันคิดว่าระเหยรับประกันความสัมพันธ์ที่เกิดขึ้นก่อน ไม่ใช่ "ล็อค" ใช่ไหม "การเขียนตัวแปรแปรปรวน (§8.3.1.4) v ประสาน - กับการอ่าน v ทุกครั้งในภายหลังโดยเธรดใด ๆ (โดยที่ลำดับถัดมาจะถูกกำหนดตามลำดับการซิงโครไนซ์)"
Shawn

15

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


3
ใช่แน่นอน! สถิตยศาสตร์ที่ไม่แน่นอนแบ่งแบ่งด้าย น่าแปลกที่ฉันไม่เคยพบอะไรเกี่ยวกับหลุมพรางนี้ทั้งใน JCiP หรือ CPJ
Julien Chastang

ฉันหวังว่านี่จะเห็นได้ชัดว่าคนที่ทำโปรแกรมพร้อมกัน สถานะโกลบอลควรเป็นที่แรกที่ตรวจสอบความปลอดภัยของเธรด
gtrak

1
@ สิ่งที่น่ากลัวคือพวกเขาไม่ได้คิดว่าพวกเขากำลังทำรายการพร้อมกัน
Tom Hawtin - tackline

15

ไม่ควรทำการโทรด้วยวิธีตามอำเภอใจจากภายในบล็อกที่ซิงโครไนซ์

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

ในกรณีนี้และโดยทั่วไปการแก้ปัญหาคือการลดขอบเขตของบล็อกที่ซิงโครไนซ์เพื่อป้องกันส่วนส่วนตัวที่สำคัญของรหัส

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


13

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

ในทางเทคนิคแล้วฉันคิดว่านี่ไม่ใช่ปัญหาการทำงานพร้อมกัน แต่เป็นปัญหาที่เกี่ยวข้องกับการใช้ไลบรารี java.util.concurrency


11

การซิงโครไนซ์ที่ไม่สมดุลโดยเฉพาะกับแผนที่ดูเหมือนจะเป็นปัญหาที่พบได้บ่อย หลายคนเชื่อว่าการซิงโครไนซ์ทำให้เป็นแผนที่ (ไม่ใช่ ConcurrentMap แต่พูด HashMap) และการซิงโครไนซ์ไม่ได้รับเพียงพอ อย่างไรก็ตามสิ่งนี้สามารถนำไปสู่การวนซ้ำไม่สิ้นสุดในระหว่างการแฮชอีกครั้ง

ปัญหาเดียวกัน (การซิงโครไนซ์บางส่วน) สามารถเกิดขึ้นได้ทุกที่ที่คุณแชร์สถานะด้วยการอ่านและเขียน


11

ฉันพบปัญหาการทำงานพร้อมกันกับ Servlets เมื่อมีฟิลด์ที่ไม่แน่นอนซึ่งจะถูกกำหนดโดยการร้องขอแต่ละครั้ง แต่มีเพียงหนึ่ง servlet-instance สำหรับคำร้องขอทั้งหมดดังนั้นสิ่งนี้จึงทำงานได้อย่างสมบูรณ์ในสภาวะแวดล้อมผู้ใช้เดียว แต่เมื่อผู้ใช้มากกว่าหนึ่งคนร้องขอ servlet ผลลัพธ์ที่ไม่สามารถคาดการณ์ได้เกิดขึ้น

public class MyServlet implements Servlet{
    private Object something;

    public void service(ServletRequest request, ServletResponse response)
        throws ServletException, IOException{
        this.something = request.getAttribute("something");
        doSomething();
    }

    private void doSomething(){
        this.something ...
    }
}

10

ไม่ใช่ข้อผิดพลาดอย่างแน่นอน แต่บาปที่เลวร้ายที่สุดคือการจัดหาห้องสมุดที่คุณต้องการให้คนอื่นใช้ แต่ไม่ได้ระบุว่าคลาส / วิธีใดที่ปลอดภัยต่อเธรด

ผู้คนจำนวนมากควรใช้คำอธิบายประกอบที่เกิดขึ้นพร้อมกัน (เช่น @ThreadSafe, @GuardedBy ฯลฯ ) ที่อธิบายไว้ในหนังสือของ Goetz


9

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

แก้ไข: ย้ายส่วนที่สองเพื่อแยกคำตอบ


คุณสามารถแยกคำตอบสุดท้ายออกเป็นคำตอบแยกได้หรือไม่? ลองเก็บ 1 โพสต์ต่อ นี่เป็นสองสิ่งที่ดีจริงๆ
Alex Miller

9

การเริ่มเธรดภายในตัวสร้างของคลาสนั้นเป็นปัญหา ถ้าคลาสถูกขยายเธรดสามารถเริ่มได้ก่อนที่ตัวสร้างคลาสย่อยจะถูกเรียกใช้งาน


8

คลาสที่ไม่แน่นอนในโครงสร้างข้อมูลที่ใช้ร่วมกัน

Thread1:
    Person p = new Person("John");
    sharedMap.put("Key", p);
    assert(p.getName().equals("John");  // sometimes passes, sometimes fails

Thread2:
    Person p = sharedMap.get("Key");
    p.setName("Alfonso");

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


8

การซิงโครไนซ์บนสตริงตัวอักษรหรือค่าคงที่ที่กำหนดโดยสตริงตัวอักษรคือ (อาจ) ปัญหาเนื่องจากสตริงตัวอักษรถูก interned และจะถูกแบ่งใช้โดยบุคคลอื่นใน JVM โดยใช้สตริงตัวอักษรเดียวกัน ฉันรู้ว่าปัญหานี้เกิดขึ้นในแอพพลิเคชันเซิร์ฟเวอร์และสถานการณ์ "คอนเทนเนอร์" อื่น ๆ

ตัวอย่าง:

private static final String SOMETHING = "foo";

synchronized(SOMETHING) {
   //
}

ในกรณีนี้ทุกคนที่ใช้สตริง "foo" เพื่อล็อคจะแชร์การล็อคเดียวกัน


อาจถูกล็อค ปัญหาคือความหมายใน WHEN สตริงถูก interned ไม่ได้กำหนด (หรือ, IMNSHO, underdefined) ค่าคงที่เวลาคอมไพเลอร์ของ "foo" ถูก interned "foo" ที่มาจากอินเทอร์เฟซเครือข่ายจะถูก interned เฉพาะเมื่อคุณทำเช่นนั้น
Kirk Wylie

นั่นคือเหตุผลที่ฉันใช้ค่าคงที่สตริงตัวอักษรโดยเฉพาะซึ่งรับประกันว่าจะถูกฝึกงาน
Alex Miller

8

ฉันเชื่อว่าในอนาคตปัญหาหลักของ Java คือการรับประกันการมองเห็น (ขาด) สำหรับผู้สร้าง ตัวอย่างเช่นถ้าคุณสร้างคลาสต่อไปนี้

class MyClass {
    public int a = 1;
}

จากนั้นเพียงแค่อ่านคุณสมบัติของ MyClass aจากเธรดอื่น MyClass.a อาจเป็น 0 หรือ 1 ขึ้นอยู่กับการนำไปใช้และอารมณ์ของ JavaVM วันนี้โอกาสสำหรับ 'a' ที่เป็น 1 สูงมาก แต่ในอนาคต NUMA เครื่องอาจแตกต่างกัน หลายคนไม่ได้ตระหนักถึงสิ่งนี้และเชื่อว่าพวกเขาไม่จำเป็นต้องสนใจมัลติเธรดในช่วงเริ่มต้น


ฉันพบว่ามันน่าแปลกใจเล็กน้อย แต่ฉันรู้ว่าคุณเป็นทิมเพื่อนที่ฉลาดดังนั้นฉันจะเอามันไปโดยไม่มีการอ้างอิง :) อย่างไรก็ตามหาก a เป็นครั้งสุดท้ายสิ่งนี้จะไม่น่าเป็นห่วงใช่ไหม จากนั้นคุณจะถูกผูกมัดด้วยความหมายตรึงสุดท้ายในระหว่างการก่อสร้าง?
Alex Miller

ฉันยังพบสิ่งต่าง ๆ ใน JMM ที่ทำให้ฉันแปลกใจดังนั้นฉันจะไม่เชื่อใจฉัน แต่ฉันค่อนข้างมั่นใจในเรื่องนี้ ดูเพิ่มเติมcs.umd.edu/~pugh/java/memoryModel/... หากฟิลด์นั้นเป็นที่สุดมันจะไม่เป็นปัญหาจากนั้นจะสามารถมองเห็นได้หลังจากขั้นตอนการเริ่มต้น
Tim Jansen

2
นี่เป็นเพียงปัญหาเท่านั้นหากการอ้างอิงของอินสแตนซ์ที่สร้างขึ้นใหม่มีการใช้งานอยู่ก่อนที่ Constructor จะส่งกลับ / เสร็จสิ้น ตัวอย่างเช่นคลาสที่ลงทะเบียนตัวเองในระหว่างการก่อสร้างในสระว่ายน้ำสาธารณะและกระทู้อื่น ๆ เริ่มเข้าถึงได้
ReneS

3
MyClass.a หมายถึงการเข้าถึงแบบคงที่และ 'a' ไม่ใช่สมาชิกแบบคงที่ของ MyClass นอกเหนือจากนั้นมันเป็นสถานะ 'ReneS' นี่เป็นปัญหาเฉพาะถ้าการอ้างอิงถึงออบเจ็กต์ที่ยังไม่เสร็จสมบูรณ์นั้นรั่วไหลออกมาเช่นการเพิ่ม 'this' ลงในแผนที่ภายนอกในคอนสตรัคเตอร์
Markus Jevring

7

ข้อผิดพลาดที่โง่ที่สุดที่ฉันทำบ่อยๆคือลืมซิงโครไนซ์ก่อนที่จะโทรแจ้ง () หรือรอ () บนวัตถุ


8
แตกต่างจากปัญหาที่เกิดขึ้นพร้อมกันส่วนใหญ่มันหาง่ายหรือไม่ อย่างน้อยคุณก็จะได้ IllegalMonitorStateException ที่นี่ ...
Outlaw Programmer

โชคดีที่มันเป็นเรื่องง่ายมากที่จะหา ... แต่ก็ยังคงเป็นความผิดพลาดโง่ที่เสียเวลาของฉันมากขึ้นกว่าที่ควร :)
เดฟเรย์

7

การใช้ "วัตถุใหม่ ()" ในท้องถิ่นเป็น mutex

synchronized (new Object())
{
    System.out.println("sdfs");
}

นี่มันไร้ประโยชน์


2
นี่อาจจะไร้ประโยชน์ แต่การกระทำการซิงโครไนซ์จะทำสิ่งที่น่าสนใจ ... การสร้างวัตถุใหม่ทุกครั้งเป็นการสูญเสียอย่างสมบูรณ์
TREE

4
มันไม่ได้ไร้ประโยชน์ มันเป็นกำแพงความทรงจำที่ไม่มีล็อค
David Roussel

1
@ David: ปัญหาเดียว - jvm สามารถเพิ่มประสิทธิภาพได้โดยการลบล็อคเลย
yetanothercoder

@insighter ฉันเห็นความคิดเห็นของคุณถูกแชร์ibm.com/developerworks/java/library/j-jtp10185/index.htmlฉันเห็นด้วยว่ามันเป็นเรื่องโง่ที่ต้องทำอย่างที่คุณไม่รู้ว่าเมื่อไรที่ความทรงจำของคุณจะประสานฉัน แค่ชี้ให้เห็นว่ากำลังทำอะไรมากกว่านั้น
David Roussel

7

ปัญหา 'การเกิดขึ้นพร้อมกัน' อีกประการหนึ่งคือการใช้รหัสที่ซิงโครไนซ์เมื่อไม่จำเป็นเลย ตัวอย่างเช่นฉันยังเห็นโปรแกรมเมอร์ใช้StringBufferหรือแม้กระทั่งjava.util.Vector(เป็นวิธีตัวแปรท้องถิ่น)


1
นี่ไม่ใช่ปัญหา แต่ไม่จำเป็นเพราะมันบอกให้ JVM ซิงค์ข้อมูลกับหน่วยความจำระดับโลกดังนั้นจึงอาจทำงานได้ไม่ดีบน multi-cpus ดังนั้นจึงไม่มีใครใช้บล็อกการซิงโครไนซ์ในเวลาเดียวกัน
ReneS

6

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


5

ไม่ทราบว่าthisในชั้นในไม่ได้thisของชั้นนอก Runnableมักจะอยู่ในระดับชั้นที่ไม่ระบุชื่อที่ใช้ ปัญหาหลักคือเนื่องจากการซิงโครไนซ์เป็นส่วนหนึ่งของทั้งหมดObjectไม่มีการตรวจสอบชนิดคงที่ได้อย่างมีประสิทธิภาพ ฉันเคยเห็นสิ่งนี้อย่างน้อยสองครั้งบน usenet และมันก็ปรากฏใน Brian Goetz'z Java Concurrency ในทางปฏิบัติ

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


3

การใช้วัตถุร่วมเช่นตัวแปรคงที่สำหรับการล็อค

สิ่งนี้นำไปสู่ประสิทธิภาพที่แย่มากเนื่องจากการช่วงชิง


ดีบางครั้งบางครั้งก็ไม่ ถ้ามันก็จะเป็นไปได้ว่าง่าย ...
gimpf

การทำเธรดจะช่วยเพิ่มประสิทธิภาพสำหรับปัญหาที่ได้รับมันจะลดประสิทธิภาพเสมอเมื่อเธรดมากกว่าหนึ่งเข้าถึงรหัสที่ได้รับการป้องกันโดยล็อค
kohlerm

3

Honesly? ก่อนการปรากฎตัวของjava.util.concurrentปัญหาที่พบบ่อยที่สุดที่ฉันพบเป็นประจำคือสิ่งที่ฉันเรียกว่า "การฟาดฟันแบบเธรด": แอปพลิเคชันที่ใช้เธรดสำหรับการทำงานพร้อมกัน แต่วางไข่จำนวนมากเกินไป


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