บล็อกที่ซิงโครไนซ์ Java เทียบกับ Collections.synchronizedMap


85

รหัสต่อไปนี้ตั้งค่าเพื่อซิงโครไนซ์การโทรอย่างถูกต้องsynchronizedMapหรือไม่?

public class MyClass {
  private static Map<String, List<String>> synchronizedMap = Collections.synchronizedMap(new HashMap<String, List<String>>());

  public void doWork(String key) {
    List<String> values = null;
    while ((values = synchronizedMap.remove(key)) != null) {
      //do something with values
    }
  }

  public static void addToMap(String key, String value) {
    synchronized (synchronizedMap) {
      if (synchronizedMap.containsKey(key)) {
        synchronizedMap.get(key).add(value);
      }
      else {
        List<String> valuesList = new ArrayList<String>();
        valuesList.add(value);
        synchronizedMap.put(key, valuesList);
      }
    }
  }
}

จากความเข้าใจของฉันฉันต้องการบล็อกที่ซิงโครไนซ์addToMap()เพื่อป้องกันไม่ให้เธรดอื่นโทรremove()หรือcontainsKey()ก่อนที่ฉันจะโทรไปput()แต่ฉันไม่ต้องการบล็อกที่ซิงโครไนซ์doWork()เนื่องจากเธรดอื่นไม่สามารถเข้าสู่บล็อกที่ซิงโครไนซ์addToMap()ก่อนที่จะremove()ส่งคืนเนื่องจากฉันสร้างแผนที่ไว้ แต่แรก ด้วยCollections.synchronizedMap(). ถูกต้องหรือไม่ มีวิธีที่ดีกว่านี้หรือไม่?

คำตอบ:


90

Collections.synchronizedMap() รับประกันว่าการดำเนินการแต่ละอะตอมที่คุณต้องการเรียกใช้บนแผนที่จะซิงโครไนซ์

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


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

2
@Yuval คุณช่วยอธิบายคำตอบของคุณในเชิงลึกอีกหน่อยได้ไหม? คุณบอกว่า sychronizedMap ทำงานแบบอะตอม แต่ทำไมคุณถึงต้องการบล็อกที่ซิงโครไนซ์ของคุณเองถ้า syncMap ทำให้การดำเนินการทั้งหมดของคุณเป็นปรมาณู? ย่อหน้าแรกของคุณดูเหมือนจะไม่ต้องกังวลเกี่ยวกับวรรคสอง
almel

@almel ดูคำตอบ
Sergey

2
ทำไมมันเป็นสิ่งจำเป็นที่จะต้องมีการทำข้อมูลให้ตรงกันบล็อกเป็นแผนที่ใช้อยู่แล้ว Collections.synchronizedMap()? ฉันไม่ได้รับคะแนนที่สอง
Bimal Sharma


13

มีโอกาสที่จะเกิดข้อบกพร่องเล็กน้อยในโค้ดของคุณ

[ อัพเดท: เนื่องจากเขาใช้ map.remove () คำอธิบายนี้ไม่ถูกต้องทั้งหมด ฉันพลาดข้อเท็จจริงนั้นในครั้งแรกที่ผ่านมา :( ขอบคุณผู้เขียนคำถามที่ชี้ให้เห็นฉันจะออกจากส่วนที่เหลือตามที่เป็นอยู่ แต่เปลี่ยนคำสั่งนำเพื่อบอกว่าอาจมีข้อบกพร่อง]

ในdoWork ()คุณจะได้รับค่า List จากแผนที่ด้วยวิธีที่ปลอดภัยต่อเธรด อย่างไรก็ตามหลังจากนั้นคุณกำลังเข้าถึงรายการนั้นในเรื่องที่ไม่ปลอดภัย ยกตัวอย่างเช่นด้ายอาจจะใช้รายการในdoWork ()ในขณะที่หัวข้ออื่นเรียกsynchronizedMap.get (กุญแจ) .add (ค่า)ในaddToMap () การเข้าถึงทั้งสองไม่ตรงกัน หลักการสำคัญคือการรับประกันความปลอดภัยต่อเธรดของคอลเลคชันจะไม่ครอบคลุมถึงคีย์หรือค่าที่เก็บไว้

คุณสามารถแก้ไขได้โดยแทรกรายการที่ซิงโครไนซ์ลงในแผนที่เช่น

List<String> valuesList = new ArrayList<String>();
valuesList.add(value);
synchronizedMap.put(key, Collections.synchronizedList(valuesList)); // sync'd list

หรือคุณสามารถซิงโครไนซ์บนแผนที่ได้ในขณะที่คุณเข้าถึงรายการในdoWork () :

  public void doWork(String key) {
    List<String> values = null;
    while ((values = synchronizedMap.remove(key)) != null) {
      synchronized (synchronizedMap) {
          //do something with values
      }
    }
  }

ตัวเลือกสุดท้ายจะ จำกัด การทำงานพร้อมกันเล็กน้อย แต่ค่อนข้างชัดเจน IMO

นอกจากนี้บันทึกย่อเกี่ยวกับ ConcurrentHashMap นี่เป็นคลาสที่มีประโยชน์จริง ๆ แต่ไม่ใช่สิ่งทดแทนที่เหมาะสมสำหรับ HashMaps ที่ซิงโครไนซ์เสมอไป อ้างจาก Javadocs

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

กล่าวอีกนัยหนึ่งคือ putIfAbsent () เหมาะสำหรับการแทรกอะตอม แต่ไม่รับประกันว่าส่วนอื่น ๆ ของแผนที่จะไม่เปลี่ยนแปลงในระหว่างการเรียกนั้น มันรับประกันเฉพาะปรมาณู ในโปรแกรมตัวอย่างของคุณคุณอาศัยรายละเอียดการซิงโครไนซ์ของ HashMap (ซิงโครไนซ์) สำหรับสิ่งอื่นที่ไม่ใช่ put ()

สิ่งสุดท้าย. :) คำพูดที่ยอดเยี่ยมจากJava Concurrency in Practiceช่วยฉันในการออกแบบการดีบักโปรแกรมแบบมัลติเธรดได้เสมอ

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


ฉันเห็นประเด็นของคุณเกี่ยวกับจุดบกพร่องหากฉันเข้าถึงรายการด้วย synchronizedMap.get () เนื่องจากฉันใช้ลบ () การเพิ่มครั้งต่อไปด้วยคีย์นั้นไม่ควรสร้าง ArrayList ใหม่และไม่ยุ่งเกี่ยวกับอันที่ฉันใช้ใน doWork ใช่หรือไม่
Ryan Ahearn

แก้ไข! ฉันลืมการลบของคุณไปแล้ว
JLR

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

11

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

synchronized (synchronizedMap) {
    if (synchronizedMap.containsKey(key)) {
        synchronizedMap.get(key).add(value);
    }
    else {
        List<String> valuesList = new ArrayList<String>();
        valuesList.add(value);
        synchronizedMap.put(key, valuesList);
    }
}

ในรหัสนี้

synchronizedMap.get(key).add(value);

และ

synchronizedMap.put(key, valuesList);

การเรียกเมธอดนั้นขึ้นอยู่กับผลลัพธ์ของก่อนหน้านี้

synchronizedMap.containsKey(key)

วิธีโทร.

หากลำดับของการเรียกเมธอดไม่ซิงโครไนซ์ผลลัพธ์อาจผิดพลาด ตัวอย่างเช่นthread 1กำลังเรียกใช้เมธอดaddToMap()และthread 2กำลังเรียกใช้เมธอดdoWork() ลำดับของการเรียกใช้เมธอดบนsynchronizedMapอ็อบเจ็กต์อาจเป็นดังนี้: Thread 1ได้ดำเนินการเมธอด

synchronizedMap.containsKey(key)

และผลลัพธ์คือ " true" หลังจากนั้นระบบปฏิบัติการได้เปลี่ยนการควบคุมการดำเนินการthread 2และดำเนินการแล้ว

synchronizedMap.remove(key)

หลังจากนั้นการควบคุมการดำเนินการได้ถูกเปลี่ยนกลับไปที่thread 1และได้ดำเนินการแล้วเช่น

synchronizedMap.get(key).add(value);

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

synchronizedMap.put(key1, valuesList1);
synchronizedMap.put(key2, valuesList2);

ที่นี่

synchronizedMap.put(key2, valuesList2);

การเรียกวิธีการไม่ขึ้นอยู่กับผลลัพธ์ของก่อนหน้านี้

synchronizedMap.put(key1, valuesList1);

การเรียกใช้วิธีการ (ไม่สนใจว่าเธรดบางส่วนจะรบกวนระหว่างการเรียกใช้วิธีการทั้งสองและตัวอย่างเช่นได้ลบออกkey1)


4

นั่นดูเหมาะกับฉัน ถ้าฉันจะเปลี่ยนแปลงอะไรฉันจะหยุดใช้ Collections.synchronizedMap () และซิงโครไนซ์ทุกอย่างในลักษณะเดียวกันเพื่อให้ชัดเจนขึ้น

ฉันจะเปลี่ยนด้วย

  if (synchronizedMap.containsKey(key)) {
    synchronizedMap.get(key).add(value);
  }
  else {
    List<String> valuesList = new ArrayList<String>();
    valuesList.add(value);
    synchronizedMap.put(key, valuesList);
  }

ด้วย

List<String> valuesList = synchronziedMap.get(key);
if (valuesList == null)
{
  valuesList = new ArrayList<String>();
  synchronziedMap.put(key, valuesList);
}
valuesList.add(value);

3
สิ่งที่ต้องทำ ฉันไม่เข้าใจว่าทำไมเราควรใช้Collections.synchronizedXXX()API เมื่อเรายังต้องซิงโครไนซ์กับวัตถุบางอย่าง (ซึ่งส่วนใหญ่จะเป็นเพียงการรวบรวมเท่านั้น) ในตรรกะของแอปทุกวันของเรา
kellogs

3

วิธีที่คุณซิงโครไนซ์ถูกต้อง แต่มีการจับ

  1. เสื้อคลุมแบบซิงโครไนซ์ที่จัดเตรียมโดยเฟรมเวิร์กการรวบรวมช่วยให้มั่นใจได้ว่าเมธอดที่เรียก Ie add / get / contains จะรันแบบพิเศษ

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

  1. คุณสามารถใช้การใช้งานแผนที่พร้อมกันในกรอบการรวบรวม ประโยชน์ของ 'ConcurrentHashMap' คือ

ก. มี API 'putIfAbsent' ซึ่งจะทำสิ่งเดียวกัน แต่มีประสิทธิภาพมากขึ้น

ข. มันมีประสิทธิภาพ: d แผนที่ CocurrentMap เพียงแค่ล็อคปุ่มดังนั้นจึงไม่ปิดกั้นโลกทั้งใบ โดยที่คุณได้บล็อกคีย์และค่าต่างๆ

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


2

ตรวจสอบGoogle คอลเลกชัน ' Multimapเช่นหน้า 28 ของงานนำเสนอนี้

หากคุณไม่สามารถใช้ไลบรารีนั้นได้ด้วยเหตุผลบางประการให้ลองใช้ConcurrentHashMapแทนSynchronizedHashMap; มีputIfAbsent(K,V)วิธีการที่ดีซึ่งคุณสามารถเพิ่มรายการองค์ประกอบแบบอะตอมได้หากยังไม่มี นอกจากนี้ให้พิจารณาใช้CopyOnWriteArrayListสำหรับค่าแผนที่หากรูปแบบการใช้งานของคุณรับประกันว่าจะทำเช่นนั้น

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