แบ่งปันหนึ่งอินสแตนซ์ของแฮชแมประหว่างอินสแตนซ์ทั้งหมดของคลาสบริการสปริง

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

@Service
public MyService{

//instance variable in spring injected service class, not sure if this correct
static final Map<String, Integer> myMap;


public void add(String key){
  Integer count = myMap.get(key);
  count++;
  myMap.put(key, count);
}

//accessed via ajax loop (and controller), if value changes update display
public Integer getCount(String key){
  return myMap.get(key)
}

@PostConstruct
public load(){
  myMap = new HashMap<String, Integer>(10){{//initialize}};
}

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


person NimChimpsky    schedule 20.06.2012    source แหล่งที่มา


คำตอบ (3)


คุณทำได้ แต่จำเป็นต้องตระหนักถึงปัญหาเหล่านั้น:

  • แผนที่ว่างเปล่าในตอนแรก แต่คุณไม่เคยตรวจสอบตัวนับว่าง
  • เมธอด add() ไม่ได้แก้ไขตัวนับในแผนที่ คุณต้องวางตัวนับกลับเข้าไปในแผนที่หลังจากเพิ่มค่าแล้ว เนื่องจากจำนวนเต็มไม่เปลี่ยนรูป หรือคุณจำเป็นต้องจัดเก็บตัวนับที่ไม่แน่นอนไว้ในแผนที่
  • มีหลายเธรดกำลังเข้าถึงแผนที่โดยไม่มีการซิงโครไนซ์ใดๆ ซึ่งจะนำไปสู่จุดบกพร่อง พฤติกรรมที่ไม่อยู่กับร่องกับรอย หรือข้อยกเว้น
  • กลยุทธ์นี้จะล้มเหลวอย่างเห็นได้ชัดในกรณีที่แอปของคุณรวมกลุ่มอยู่ในเซิร์ฟเวอร์หลายเครื่อง
person JB Nizet    schedule 20.06.2012
comment
ฉันได้แก้ไขปัญหาเล็กๆ น้อยๆ ออกไปแล้ว แต่วิธีที่ดีที่สุดในการแก้ปัญหาหลายๆ เธรดคือการเข้าถึงแผนที่โดยไม่มีการซิงโครไนซ์ใดๆ ซึ่งจะนำไปสู่จุดบกพร่อง พฤติกรรมที่ไม่อยู่กับร่องกับรอย หรือข้อยกเว้น - person NimChimpsky; 20.06.2012
comment
ในแอปพลิเคชันจริง มีมากกว่าเธรดหลักที่สามารถเข้าถึงทรัพยากรที่ใช้ร่วมกันของคุณ (โดยเฉพาะใน Web Applications) ดังนั้นหนึ่งเธรดสามารถใส่วัตถุ A ด้วยคีย์คีย์ ในขณะที่อีกเธรดหนึ่งจะแทรกวัตถุ B ด้วยคีย์คีย์ในเวลาเดียวกัน เวลา. ในกรณีของแผนที่ คุณสามารถใช้ java util.concurrent.ConcurrentHashMap เพื่อจัดการปัญหาเหล่านี้ - person Luiggi Mendoza; 20.06.2012
comment
ถูกต้อง การใช้ ConcurrentMap ที่จัดเก็บอินสแตนซ์ AtomicInteger เป็นวิธีแก้ปัญหา แต่คุณจำเป็นต้องเข้าใจปัญหาเธรดที่อาจเกิดขึ้นทั้งหมดก่อนที่จะดำเนินการใดๆ อ่าน การทำงานพร้อมกันของ Java ในทางปฏิบัติ - person JB Nizet; 20.06.2012
comment
@JBNizet ฉันไม่สามารถสร้างคลาสเพื่อล้อมแผนที่ ใส่คำอธิบายประกอบเป็นที่เก็บข้อมูลและฉีดเข้าไปได้ ? - person NimChimpsky; 20.06.2012
comment
ใช่ คุณสามารถทำได้ ตามที่ฉันพูดในคำตอบของฉัน แต่เนื่องจาก bean จะเข้าถึงได้หลายเธรด คุณต้องทำให้เธรดปลอดภัยและถูกต้อง - person JB Nizet; 20.06.2012
comment
@JBNizet ตกลงเป็นอย่างไรบ้าง:stackoverflow.com/q/11126684/106261 ขอบคุณสำหรับความช่วยเหลือ btw - person NimChimpsky; 20.06.2012

ใช้ ConcurrentHashMap

public void add(String key){
    Integer count = myMap.get(key);
    count= count++;
    myMap.put(key, count);
}
person Subin Sebastian    schedule 20.06.2012
comment
นี่ยังคงเป็นรหัสที่ไม่ถูกต้อง ไม่มีปัญหาการซิงโครไนซ์ แต่คุณอาจมีสามเธรดเรียกใช้เมธอด add พร้อมกัน และมีค่าเพิ่มขึ้นทีละ 1 เท่านั้น - person JB Nizet; 20.06.2012
comment
ใช่คุณถูกต้อง. ฉันคิดว่าต้องทำให้วิธีการซิงโครไนซ์กันใช่ไหม - person Subin Sebastian; 20.06.2012
comment
นั่นเป็นวิธีหนึ่งในการทำให้โค้ดถูกต้อง โดยมีเงื่อนไขว่าวิธีอื่นๆ ในการเข้าถึงแผนที่จะต้องซิงโครไนซ์กันด้วย แต่ถ้าทำเสร็จแล้ว การใช้ ConcurrentMap ก็ไม่มีข้อได้เปรียบเหนือการใช้ HashMap แบบธรรมดา - person JB Nizet; 20.06.2012
comment
วิธีการบริการซิงโครไนซ์เป็นสิ่งที่เราต้องหลีกเลี่ยง - person Subin Sebastian; 20.06.2012

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

public void add(String key){
  Integer count = myMap.get(key);
  count++;
  myMap.put(key, count);
}

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

@PostConstruct
public load(){
  myMap = new HashMap<String, Integer>(10){{//initialize}};
  myMap = Collections.synchronizedMap(myMap);
}
person Michael    schedule 20.06.2012
comment
มันจะทำให้เธรด Map ปลอดภัย แต่เมธอด add() ที่ไม่ใช่อะตอมมิกยังคงไม่ปลอดภัยสำหรับเธรด - person nicholas.hauschild; 20.06.2012
comment
@ nicholas.hauschild โอ้ใช่แล้ว นั่นเป็นเรื่องจริง คุณจะต้องทำให้วิธีการ add() ซิงโครไนซ์หรือรวมเนื้อหาไว้ในบล็อก synchronized(myMap) - person Michael; 20.06.2012