สวัสดี! วันนี้เราจะพิจารณาคุณสมบัติของการเขียนโปรแกรมแบบมัลติเธรดต่อไป และพูดคุยเกี่ยวกับการซิงโครไนซ์เธรด
การซิงโครไนซ์ใน Java คืออะไร?
ภายนอกโดเมนการเขียนโปรแกรม หมายถึงการจัดการที่อนุญาตให้อุปกรณ์หรือโปรแกรมสองเครื่องทำงานร่วมกัน ตัวอย่างเช่น สมาร์ทโฟนและคอมพิวเตอร์สามารถซิงโครไนซ์กับบัญชี Google และบัญชีเว็บไซต์สามารถซิงโครไนซ์กับบัญชีโซเชียลเน็ตเวิร์กได้ ดังนั้นคุณจึงลงชื่อเข้าใช้บัญชีเหล่านี้ได้ การซิงโครไนซ์เธรดมีความหมายคล้ายกัน นั่นคือการจัดเรียงที่เธรดโต้ตอบกับ กันและกัน. ในบทเรียนก่อนหน้านี้ เธรดของเราอาศัยและทำงานแยกจากกัน คนหนึ่งทำการคำนวณ คนที่สองหลับ และคนที่สามแสดงบางอย่างบนคอนโซล แต่พวกเขาไม่ได้โต้ตอบ ในโปรแกรมจริง สถานการณ์เช่นนี้เกิดขึ้นได้ยาก หลายเธรดสามารถทำงานและแก้ไขชุดข้อมูลเดียวกันได้ สิ่งนี้สร้างปัญหา ลองนึกภาพหลายๆ เธรดที่เขียนข้อความไปยังที่เดียวกัน เช่น ไปยังไฟล์ข้อความหรือคอนโซล ในกรณีนี้ ไฟล์หรือคอนโซลจะกลายเป็นทรัพยากรที่ใช้ร่วมกัน เธรดไม่รู้จักการดำรงอยู่ของกันและกัน ดังนั้นพวกเขาจึงเขียนทุกอย่างที่ทำได้ในเวลาที่กำหนดโดยตัวกำหนดตารางเวลาเธรด ในบทเรียนเมื่อเร็วๆ นี้ เราเห็นตัวอย่างว่าสิ่งนี้นำไปสู่ที่ใด จำตอนนี้: เหตุผลอยู่ที่ความจริงที่ว่าเธรดกำลังทำงานกับทรัพยากรที่ใช้ร่วมกัน (คอนโซล) โดยไม่ประสานการกระทำซึ่งกันและกัน หากตัวกำหนดตารางเวลาของเธรดจัดสรรเวลาให้กับเธรด-1 ก็จะเขียนทุกอย่างลงในคอนโซลทันที สิ่งที่เธรดอื่นมีหรือยังไม่ได้จัดการในการเขียนไม่สำคัญ ผลลัพธ์อย่างที่คุณเห็นคือน่าหดหู่ใจ นั่นเป็นเหตุผลที่พวกเขาแนะนำแนวคิดพิเศษmutex (การยกเว้นซึ่งกันและกัน)ในการเขียนโปรแกรมแบบมัลติเธรด วัตถุประสงค์ของมิวเท็กซ์คือการจัดเตรียมกลไกเพื่อให้มีเพียงหนึ่งเธรดที่สามารถเข้าถึงออบเจกต์ได้ในเวลาหนึ่งๆ หากเธรด-1 ได้รับ mutex ของวัตถุ A เธรดอื่นจะไม่สามารถเข้าถึงและแก้ไขวัตถุได้ เธรดอื่นต้องรอจนกว่า mutex ของวัตถุ A จะถูกปล่อย นี่คือตัวอย่างจากชีวิต: ลองนึกภาพว่าคุณและคนแปลกหน้าอีก 10 คนกำลังเข้าร่วมในการออกกำลังกาย ผลัดกัน คุณต้องแสดงความคิดของคุณและหารือเกี่ยวกับบางสิ่งบางอย่าง แต่เนื่องจากคุณเพิ่งเจอกันเป็นครั้งแรก เพื่อไม่ให้เกิดการขัดจังหวะกันและโกรธเกรี้ยว คุณจึงใช้ 'ลูกบอลพูด': เฉพาะผู้ที่มีลูกบอลเท่านั้นที่สามารถพูดได้ ด้วยวิธีนี้คุณจะได้การสนทนาที่ดีและเกิดผล โดยพื้นฐานแล้ว ลูกบอลเป็นมิวเท็กซ์ ถ้า mutex ของอ็อบเจ็กต์อยู่ในมือของเธรดหนึ่ง เธรดอื่นๆ จะไม่สามารถทำงานกับอ็อบเจ็กต์ได้Object
คลาสซึ่งหมายความว่าทุกวัตถุใน Java มีหนึ่ง
วิธีการทำงานของตัวดำเนินการซิงโครไนซ์
มาทำความรู้จักกับคีย์เวิร์ดใหม่: ซิงโครไนซ์ มันถูกใช้เพื่อทำเครื่องหมายบล็อกของรหัส ถ้าบล็อกโค้ดถูกทำเครื่องหมายด้วยsynchronized
คีย์เวิร์ด บล็อกนั้นจะดำเนินการได้ครั้งละหนึ่งเธรดเท่านั้น การซิงโครไนซ์สามารถทำได้หลายวิธี ตัวอย่างเช่น โดยการประกาศเมธอดทั้งหมดที่จะซิงโครไนซ์:
public synchronized void doSomething() {
// ...Method logic
}
หรือเขียนบล็อกรหัสที่ดำเนินการซิงโครไนซ์โดยใช้วัตถุบางอย่าง:
public class Main {
private Object obj = new Object();
public void doSomething() {
// ...Some logic available simultaneously to all threads
synchronized (obj) {
// Logic available to just one thread at a time
}
}
}
ความหมายนั้นง่าย หากเธรดหนึ่งเข้าไปภายในบล็อคโค้ดที่มีsynchronized
คำสำคัญกำกับไว้ เธรดนั้นจะจับ mutex ของอ็อบเจ็กต์ทันที และเธรดอื่นๆ ทั้งหมดที่พยายามเข้าสู่บล็อคหรือเมธอดเดียวกันจะถูกบังคับให้รอจนกว่าเธรดก่อนหน้าจะทำงานเสร็จและปล่อยมอนิเตอร์ อนึ่ง! ในระหว่างหลักสูตร คุณได้เห็นตัวอย่างแล้วsynchronized
แต่ดูแตกต่างออกไป:
public void swap()
{
synchronized (this)
{
// ...Method logic
}
}
หัวข้อนี้ใหม่สำหรับคุณ และแน่นอนว่าจะเกิดความสับสนกับไวยากรณ์ ดังนั้นให้จำทันทีเพื่อไม่ให้สับสนในภายหลังจากวิธีการเขียนแบบต่างๆ วิธีเขียนทั้งสองนี้มีความหมายเหมือนกัน:
public void swap() {
synchronized (this)
{
// ...Method logic
}
}
public synchronized void swap() {
}
}
ในกรณีแรก คุณจะสร้างบล็อกโค้ดที่ซิงโครไนซ์ทันทีที่ป้อนเมธอด มันถูกซิงโครไนซ์โดยthis
วัตถุ กล่าวคือ วัตถุปัจจุบัน และในตัวอย่างที่สอง คุณใช้synchronized
คีย์เวิร์ดกับเมธอดทั้งหมด ทำให้ไม่จำเป็นต้องระบุวัตถุที่ใช้สำหรับการซิงโครไนซ์อย่างชัดเจน เนื่องจากเมธอดทั้งหมดถูกทำเครื่องหมายด้วยคีย์เวิร์ด เมธอดจะถูกซิงโครไนซ์โดยอัตโนมัติสำหรับทุกอินสแตนซ์ของคลาส เราจะไม่ถกกันว่าทางไหนดีกว่ากัน สำหรับตอนนี้ เลือกวิธีใดก็ได้ที่คุณชอบที่สุด :) สิ่งสำคัญที่ต้องจำไว้: คุณสามารถประกาศวิธีการที่ซิงโครไนซ์ได้ก็ต่อเมื่อตรรกะทั้งหมดของมันถูกดำเนินการโดยเธรดหนึ่งครั้ง ตัวอย่างเช่น การซิงโครไนซ์เมธอดต่อไปนี้อาจเป็นข้อผิดพลาดdoSomething()
:
public class Main {
private Object obj = new Object();
public void doSomething() {
// ...Some logic available simultaneously to all threads
synchronized (obj) {
// Logic available to just one thread at a time
}
}
}
อย่างที่คุณเห็น ส่วนหนึ่งของวิธีการมีตรรกะที่ไม่ต้องการการซิงโครไนซ์ โค้ดนั้นสามารถเรียกใช้โดยหลายเธรดในเวลาเดียวกัน และตำแหน่งที่สำคัญทั้งหมดจะถูกแยกออกจากกันในsynchronized
บล็อก แยกต่างหาก และอีกสิ่งหนึ่ง ลองพิจารณาตัวอย่างของเราอย่างใกล้ชิดจากบทเรียนที่มีการสลับชื่อกัน:
public void swap()
{
synchronized (this)
{
// ...Method logic
}
}
หมายเหตุ:การซิงโครไนซ์ดำเนินการโดยใช้this
. นั่นคือการใช้MyClass
วัตถุสมมติว่าเรามี 2 เธรด (Thread-1
และThread-2
) และMyClass myClass
วัตถุในกรณีนี้ หากThread-1
เรียกใช้myClass.swap()
เมธอด mutex ของออบเจ็กต์จะไม่ว่าง และเมื่อพยายามเรียกใช้myClass.swap()
เมธอดThread-2
จะหยุดทำงานขณะรอการเผยแพร่ mutex หากเราจะมี 2 เธรดและ 2MyClass
ออบเจ็กต์ (myClass1
และmyClass2
) เธรดของเราสามารถเรียกใช้เมธอดที่ซิงโครไนซ์พร้อมกันบนออบเจ็กต์ต่างๆ ได้อย่างง่ายดาย เธรดแรกดำเนินการนี้:
myClass1.swap();
ที่สองดำเนินการนี้:
myClass2.swap();
ในกรณีนี้synchronized
คำหลักภายในswap()
เมธอดจะไม่ส่งผลต่อการทำงานของโปรแกรม เนื่องจากการซิงโครไนซ์จะดำเนินการโดยใช้วัตถุเฉพาะ และในกรณีหลัง เรามีวัตถุ 2 ชิ้น กระทู้จึงไม่สร้างปัญหาให้กัน ท้ายที่สุดแล้ว วัตถุ 2 ชิ้น มี 2 mutexes ที่แตกต่างกัน และการได้มาซึ่งสิ่งหนึ่งนั้นไม่ขึ้นอยู่กับการได้มาซึ่งอีกสิ่งหนึ่ง
คุณสมบัติพิเศษของการซิงโครไนซ์ในวิธีคงที่
แต่ถ้าคุณต้องการซิงโครไนซ์เมธอดแบบคง ที่ ล่ะ
class MyClass {
private static String name1 = "Ally";
private static String name2 = "Lena";
public static synchronized void swap() {
String s = name1;
name1 = name2;
name2 = s;
}
}
ยังไม่ชัดเจนว่า mutex จะมีบทบาทอย่างไรที่นี่ ท้ายที่สุด เราได้พิจารณาแล้วว่าแต่ละออบเจกต์มีมิวเท็กซ์ แต่ปัญหาคือเราไม่ต้องการให้วัตถุเรียกMyClass.swap()
เมธอด: เมธอดเป็นแบบสแตติก! แล้วอะไรต่อไป? :/ ที่จริงไม่มีปัญหาที่นี่ ผู้สร้างของ Java ดูแลทุกอย่าง :) หากเมธอดที่มีตรรกะพร้อมกันที่สำคัญเป็นแบบสแตติก การซิงโครไนซ์จะดำเนินการที่ระดับคลาส เพื่อความชัดเจนยิ่งขึ้น เราสามารถเขียนโค้ดด้านบนใหม่ได้ดังนี้:
class MyClass {
private static String name1 = "Ally";
private static String name2 = "Lena";
public static void swap() {
synchronized (MyClass.class) {
String s = name1;
name1 = name2;
name2 = s;
}
}
}
โดยหลักการแล้ว คุณอาจคิดเรื่องนี้ได้เอง: เนื่องจากไม่มีออบเจกต์ กลไกการซิงโครไนซ์จะต้องถูกรวมเข้ากับคลาสด้วยวิธีการใดวิธีหนึ่ง และนั่นคือวิธีการ: เราสามารถใช้คลาสเพื่อซิงโครไนซ์
GO TO FULL VERSION