threadsafe หมายถึงอะไร?


124

เมื่อเร็ว ๆ นี้ฉันพยายามเข้าถึงกล่องข้อความจากเธรด (นอกเหนือจากเธรด UI) และมีข้อยกเว้นเกิดขึ้น มันพูดบางอย่างเกี่ยวกับ "รหัสไม่ปลอดภัยเธรด" ดังนั้นฉันจึงลงเอยด้วยการเขียนตัวแทน (ตัวอย่างจาก MSDN ช่วย) และเรียกมันแทน

แต่ถึงอย่างนั้นฉันก็ไม่ค่อยเข้าใจว่าทำไมต้องใช้รหัสพิเศษทั้งหมด

อัปเดต: ฉันจะประสบปัญหาร้ายแรงหรือไม่หากตรวจสอบ

Controls.CheckForIllegalCrossThread..blah =true

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


@dave ขออภัยฉันพยายามค้นหา แต่ก็ยอมแพ้ ... ขอบคุณต่อไป ..
Vivek Bernard

1
รหัสที่ไม่เกิดขึ้นRace-Condition
Muhammad Babar

คำตอบ:


121

Eric Lippertมีบล็อกโพสต์ดีๆชื่อสิ่งที่คุณเรียกว่า "thread safe" คืออะไร? เกี่ยวกับคำจำกัดความของความปลอดภัยของเธรดที่พบใน Wikipedia

3 สิ่งสำคัญที่ดึงมาจากลิงก์:

“ โค้ดส่วนหนึ่งจะปลอดภัยต่อเธรดหากทำงานได้อย่างถูกต้องระหว่างการดำเนินการพร้อมกันหลายเธรด”

“ โดยเฉพาะอย่างยิ่งต้องตอบสนองความต้องการสำหรับหลายเธรดเพื่อเข้าถึงข้อมูลที่แชร์เดียวกัน…”

“ …และความจำเป็นในการเข้าถึงข้อมูลร่วมกันโดยใช้เธรดเพียงชุดเดียวในช่วงเวลาใดเวลาหนึ่ง”

คุ้มค่าแก่การอ่าน!


25
โปรดหลีกเลี่ยงคำตอบเฉพาะลิงก์เพราะอาจกลายเป็นผลเสียได้ทุกเมื่อในอนาคต
akhil_mittal

1
ลิงก์ที่อัปเดต: docs.microsoft.com/en-nz/archive/blogs/ericlippert/…
Ryan Buddicom

106

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


11
นี้เรียกว่าการซิงโครไนซ์ ขวา?
JavaTechnical

3
ใช่. การบังคับให้เธรดต่างๆรอการเข้าถึงทรัพยากรที่แชร์สามารถทำได้ด้วยการซิงโครไนซ์
Vincent Ramdhanie

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

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

สิ่งนี้ใช้ได้กับโค้ดที่ใช้ตัวแปรส่วนกลางและตัวแปรคงที่เท่านั้นหรือไม่? การใช้ตัวอย่างของคุณเกี่ยวกับบุคคลที่แก้ไขเอกสารฉันคิดว่ามันไม่สมเหตุสมผลที่จะป้องกันไม่ให้บุคคลที่ 2 เรียกใช้รหัสการเขียนเอกสารในเอกสารอื่น
Aaron Franke

18

Wikipediaมีบทความเกี่ยวกับ Thread Safety

นี้หน้าคำจำกัดความ (คุณต้องข้ามโฆษณา - ขอโทษ) กำหนดมันได้ดังนี้:

ในการเขียนโปรแกรมคอมพิวเตอร์ thread-safe อธิบายถึงส่วนของโปรแกรมหรือรูทีนที่สามารถเรียกได้จากเธรดการเขียนโปรแกรมหลายเธรดโดยไม่มีการโต้ตอบที่ไม่ต้องการระหว่างเธรด

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

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

เนื่องจากเธรดเหล่านี้มักจะต้องดำเนินการทั่วไปเช่นดิสก์ i / o การแสดงผลลัพธ์ไปยังหน้าจอเป็นต้น - ส่วนเหล่านี้ของโค้ดจะต้องเขียนในลักษณะที่สามารถจัดการกับการเรียกจากเธรดหลายเธรดได้บ่อยครั้งที่ ในเวลาเดียวกัน. สิ่งนี้จะเกี่ยวข้องกับสิ่งต่างๆเช่น:

  • ทำงานกับสำเนาข้อมูล
  • การเพิ่มล็อครอบรหัสวิกฤต

8

เพียงแค่เธรดปลอดภัยหมายความว่าเมธอดหรือคลาสอินสแตนซ์สามารถใช้กับเธรดหลายเธรดพร้อมกันได้โดยไม่มีปัญหาใด ๆ เกิดขึ้น

พิจารณาวิธีการต่อไปนี้:

private int myInt = 0;
public int AddOne()
{
    int tmp = myInt;
    tmp = tmp + 1;
    myInt = tmp;
    return tmp;
}

ตอนนี้เธรด A และเธรด B ทั้งสองต้องการเรียกใช้ AddOne () แต่ A เริ่มต้นก่อนและอ่านค่าของ myInt (0) เป็น tmp ตอนนี้ด้วยเหตุผลบางอย่างตัวกำหนดตารางเวลาตัดสินใจหยุดเธรด A และเลื่อนการดำเนินการไปยังเธรด B เธรด B ตอนนี้อ่านค่าของ myInt (ยังคงเป็น 0) เป็นตัวแปร tmp ของตัวเอง เธรด B เสร็จสิ้นวิธีการทั้งหมดดังนั้นในที่สุด myInt = 1 และ 1 จะถูกส่งกลับ ตอนนี้ถึงคราวของ Thread A อีกครั้ง เธรด A ดำเนินต่อไป และเพิ่ม 1 เป็น tmp (tmp คือ 0 สำหรับเธรด A) จากนั้นบันทึกค่านี้ใน myInt myInt เป็นอีกครั้ง 1.

ดังนั้นในกรณีนี้เมธอด AddOne จึงถูกเรียกสองครั้ง แต่เนื่องจากวิธีนี้ไม่ได้ถูกนำไปใช้ในเธรดอย่างปลอดภัยค่าของ myInt ไม่ใช่ 2 ตามที่คาดไว้ แต่เป็น 1 เนื่องจากเธรดที่สองอ่านตัวแปร myInt ก่อนที่เธรดแรกจะเสร็จสิ้น อัปเดต

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


ถ้าเมธอด AddOne ถูกเรียกสองครั้ง
Sujith PS

7

ตัวอย่างในโลกแห่งความเป็นจริงสำหรับฆราวาสคือ

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

Threadsafe หมายความว่าสถานะของอ็อบเจ็กต์จะไม่เปลี่ยนแปลงหากเธรดหลายเธรดพยายามเข้าถึงอ็อบเจ็กต์พร้อมกัน


5

คุณสามารถรับคำอธิบายเพิ่มเติมได้จากหนังสือ "Java Concurrency in Practice":

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


4

โมดูลมีความปลอดภัยต่อเธรดหากรับประกันว่าสามารถรักษาค่าคงที่เมื่อเผชิญกับการใช้งานแบบมัลติเธรดและการใช้งานพร้อมกัน

ที่นี่โมดูลสามารถเป็นโครงสร้างข้อมูลคลาสวัตถุวิธีการ / ขั้นตอนหรือฟังก์ชัน ขอบเขตของโค้ดและข้อมูลที่เกี่ยวข้องโดยทั่วไป

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

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

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


3

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

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

Lock Objectsรองรับการล็อกสำนวนที่ช่วยลดความซับซ้อนของแอพพลิเคชั่นพร้อมกันจำนวนมาก

ผู้ดำเนินการกำหนด API ระดับสูงสำหรับการเรียกใช้และจัดการเธรด การใช้งานตัวดำเนินการที่จัดเตรียมโดย java.util.concurrent ให้การจัดการเธรดพูลที่เหมาะสมกับแอปพลิเคชันขนาดใหญ่

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

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

ThreadLocalRandom (ใน JDK 7) ให้การสร้างหมายเลขเทียมที่มีประสิทธิภาพจากหลายเธรด

อ้างถึงjava.util.concurrentและjava.util.concurrent.atomicแพคเกจเกินไปสำหรับการเขียนโปรแกรมโครงสร้างอื่น ๆ


1

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

แนวทางปฏิบัติปกติของ WinForms คือการมีเธรดเดียวที่ทุ่มเทให้กับงาน UI ทั้งหมดของคุณ


1

ฉันพบว่าแนวคิดของhttp://en.wikipedia.org/wiki/Reentrancy_%28computing%29เป็นสิ่งที่ฉันมักคิดว่าเป็นเธรดที่ไม่ปลอดภัยซึ่งเมื่อวิธีการมีและอาศัยผลข้างเคียงเช่นตัวแปรส่วนกลาง

ตัวอย่างเช่นฉันเคยเห็นรหัสที่จัดรูปแบบตัวเลขทศนิยมเป็นสตริงหากสองสิ่งเหล่านี้ถูกเรียกใช้ในเธรดที่ต่างกันค่าส่วนกลางของ decimalSeparator สามารถเปลี่ยนเป็น "." ได้อย่างถาวร

//built in global set to locale specific value (here a comma)
decimalSeparator = ','

function FormatDot(value : real):
    //save the current decimal character
    temp = decimalSeparator

    //set the global value to be 
    decimalSeparator = '.'

    //format() uses decimalSeparator behind the scenes
    result = format(value)

    //Put the original value back
    decimalSeparator = temp

-2

เพื่อทำความเข้าใจเกี่ยวกับความปลอดภัยของเธรดโปรดอ่านหัวข้อด้านล่าง:

4.3.1 ตัวอย่าง: Vehicle Tracker โดยใช้ Delegation

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

รายการ 4.6. คลาสจุดไม่เปลี่ยนรูปที่ใช้โดย DelegatingVehicleTracker

 class Point{
  public final int x, y;

  public Point() {
        this.x=0; this.y=0;
    }

  public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

}

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

DelegatingVehicleTrackerในรายการ 4.7 ไม่ได้ใช้การซิงโครไนซ์ที่ชัดเจนใด ๆ การเข้าถึงสถานะทั้งหมดได้รับการจัดการConcurrentHashMapและคีย์และค่าทั้งหมดของแผนที่ไม่เปลี่ยนรูป

รายการ 4.7. การมอบหมายความปลอดภัยของเธรดให้กับ ConcurrentHashMap

  public class DelegatingVehicleTracker {

  private final ConcurrentMap<String, Point> locations;
    private final Map<String, Point> unmodifiableMap;

  public DelegatingVehicleTracker(Map<String, Point> points) {
        this.locations = new ConcurrentHashMap<String, Point>(points);
        this.unmodifiableMap = Collections.unmodifiableMap(locations);
    }

  public Map<String, Point> getLocations(){
        return this.unmodifiableMap; // User cannot update point(x,y) as Point is immutable
    }

  public Point getLocation(String id) {
        return locations.get(id);
    }

  public void setLocation(String id, int x, int y) {
        if(locations.replace(id, new Point(x, y)) == null) {
             throw new IllegalArgumentException("invalid vehicle name: " + id);
        }
    }

}

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

4.3.2 ตัวแปรสถานะอิสระ

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

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

รายการ 4.9. การมอบหมายความปลอดภัยของเธรดให้กับตัวแปรสถานะพื้นฐานหลายตัวแปร

public class VisualComponent {
    private final List<KeyListener> keyListeners 
                                        = new CopyOnWriteArrayList<KeyListener>();
    private final List<MouseListener> mouseListeners 
                                        = new CopyOnWriteArrayList<MouseListener>();

  public void addKeyListener(KeyListener listener) {
        keyListeners.add(listener);
    }

  public void addMouseListener(MouseListener listener) {
        mouseListeners.add(listener);
    }

  public void removeKeyListener(KeyListener listener) {
        keyListeners.remove(listener);
    }

  public void removeMouseListener(MouseListener listener) {
        mouseListeners.remove(listener);
    }

}

VisualComponentใช้CopyOnWriteArrayListเพื่อจัดเก็บรายชื่อผู้ฟังแต่ละคน นี่คือการใช้งานรายการที่ปลอดภัยสำหรับเธรดที่เหมาะอย่างยิ่งสำหรับการจัดการรายการผู้ฟัง (ดูหัวข้อ 5.2.3) แต่ละรายการมีความปลอดภัยต่อเธรดและเนื่องจากไม่มีข้อ จำกัด ในการเชื่อมต่อสถานะของหนึ่งกับสถานะของอีกสถานะหนึ่งจึงVisualComponentสามารถมอบหมายความรับผิดชอบด้านความปลอดภัยของเธรดให้กับสิ่งที่อยู่ภายใต้mouseListenersและkeyListenersวัตถุได้

4.3.3 เมื่อการมอบสิทธิ์ล้มเหลว

คลาสคอมโพสิตส่วนใหญ่ไม่ง่ายอย่างที่VisualComponent: พวกเขามีค่าคงที่ที่เกี่ยวข้องกับตัวแปรสถานะองค์ประกอบของพวกเขา NumberRangeในรายการ 4.10 ใช้สองAtomicIntegersเพื่อจัดการสถานะของมัน แต่กำหนดข้อ จำกัด เพิ่มเติมนั่นคือตัวเลขแรกน้อยกว่าหรือเท่ากับตัวที่สอง

รายการ 4.10. คลาสช่วงจำนวนที่ไม่สามารถป้องกันค่าคงที่ได้อย่างเพียงพอ อย่าทำแบบนี้

public class NumberRange {

  // INVARIANT: lower <= upper
    private final AtomicInteger lower = new AtomicInteger(0);
    private final AtomicInteger upper = new AtomicInteger(0);

  public void setLower(int i) {
        //Warning - unsafe check-then-act
        if(i > upper.get()) {
            throw new IllegalArgumentException(
                    "Can't set lower to " + i + " > upper ");
        }
        lower.set(i);
    }

  public void setUpper(int i) {
        //Warning - unsafe check-then-act
        if(i < lower.get()) {
            throw new IllegalArgumentException(
                    "Can't set upper to " + i + " < lower ");
        }
        upper.set(i);
    }

  public boolean isInRange(int i){
        return (i >= lower.get() && i <= upper.get());
    }

}

NumberRangeคือไม่ด้ายปลอดภัย ; มันไม่ได้รักษาค่าคงที่ที่ จำกัด ด้านล่างและด้านบน setLowerและsetUpperวิธีการพยายามที่จะเคารพคงที่นี้ แต่ทำได้ไม่ดี ทั้งสองsetLowerและsetUpperเป็นลำดับที่ตรวจสอบแล้วดำเนินการ แต่ไม่ได้ใช้การล็อกที่เพียงพอที่จะทำให้เป็นอะตอม หากช่วงตัวเลขมีค่า (0, 10) และมีการเรียกเธรดหนึ่งsetLower(5)ในขณะที่อีกเธรดหนึ่งเรียกsetUpper(4)ด้วยเวลาที่โชคร้ายทั้งสองจะผ่านการตรวจสอบในตัวตั้งค่าและการแก้ไขทั้งสองจะถูกนำไปใช้ ผลที่ได้คือว่าช่วงนี้ถือ (5, 4) - เป็นรัฐที่ไม่ถูกต้อง ดังนั้นในขณะที่ AtomicIntegers ต้นแบบด้ายปลอดภัยชั้นคอมโพสิตไม่ เนื่องจากตัวแปรสถานะพื้นฐานlowerและupperไม่เป็นอิสระไม่NumberRangeสามารถมอบหมายความปลอดภัยของเธรดให้กับตัวแปรสถานะเธรดที่ปลอดภัยได้

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

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

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

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