สวัสดี! ในขณะที่ศึกษามัลติเธรดใน JavaRush คุณมักจะเจอแนวคิดของ "mutex" และ "monitor" ตอนนี้คุณสามารถตอบได้โดยไม่ต้องมองว่ามันแตกต่างกันอย่างไร? :) ถ้าทำได้ ทำได้ดีมาก! ถ้าไม่ (และมักเกิดเหตุการณ์นี้ขึ้น) ก็ไม่น่าแปลกใจเลย แนวคิดของ "mutex" และ "monitor" มีความเกี่ยวข้องกันอย่างแน่นอน นอกจากนี้ ขณะที่อ่านการบรรยายและดูวิดีโอเกี่ยวกับมัลติเธรดจากแหล่งข้อมูลภายนอกบนอินเทอร์เน็ต คุณจะพบกับแนวคิดที่คล้ายกันอีกประการหนึ่ง - "เซมาฟอร์" ฟังก์ชันการทำงานของมันยังคล้ายกับจอภาพและ mutex เป็นอย่างมาก ดังนั้น มาทำความเข้าใจคำศัพท์ทั้งสามคำนี้ ดูตัวอย่างเล็กๆ น้อยๆ และสุดท้ายก็จัดระเบียบความเข้าใจในหัวของเราว่าพวกเขาแตกต่างกันอย่างไร :)
เราจะลดความซับซ้อนของเงื่อนไขลงเล็กน้อยเพื่อความเข้าใจที่ดีขึ้น ลองนึกภาพเรามีนักปรัชญา 5 คนที่ต้องการอาหารกลางวัน ในเวลาเดียวกัน เรามีโต๊ะหนึ่งตัว และสามารถรองรับคนได้ไม่เกินสองคนในเวลาเดียวกัน หน้าที่ของเราคือการเลี้ยงนักปรัชญาทุกคน ทั้งคู่ไม่ควรหิว และไม่ควร “ขวาง” กันเมื่อพยายามนั่งลงที่โต๊ะ (เราต้องหลีกเลี่ยงการหยุดชะงัก) ชั้นเรียนนักปรัชญาของเราจะมีลักษณะดังนี้:
มูเท็กซ์
mutex เป็นอ็อบเจ็กต์พิเศษสำหรับการซิงโครไนซ์เธรด มัน “แนบ” กับทุกอ็อบเจ็กต์ใน Java - คุณรู้อยู่แล้วว่า :) มันไม่สำคัญว่าคุณจะใช้คลาสมาตรฐานหรือสร้างคลาสของคุณเอง เช่นCat
และDog
: ออบเจ็กต์ ทั้งหมดของทุกคลาสจะมี mutex ชื่อ "mutex" มาจากภาษาอังกฤษ "MUTual EXclusion" - "mutual exclusion" และสิ่งนี้สะท้อนถึงวัตถุประสงค์ได้อย่างสมบูรณ์แบบ ดังที่เราได้กล่าวไว้ในการบรรยายครั้งก่อนๆหน้าที่ของ mutex คือการจัดหากลไกดังกล่าวเพื่อให้มีเธรดเดียวเท่านั้นที่สามารถเข้าถึงอ็อบเจ็กต์ได้ในช่วงเวลาหนึ่ง คำเปรียบเทียบยอดนิยมสำหรับ mutex ในชีวิตจริงคือ "ตัวอย่างห้องน้ำ" เมื่อมีคนเข้าห้องน้ำเขาจะล็อคประตูจากด้านใน ห้องน้ำทำหน้าที่เป็นวัตถุที่สามารถเข้าถึงได้โดยใช้หลายเธรด ล็อคประตูห้องน้ำเป็นบทบาทของ mutex และคิวของคนภายนอกเป็นบทบาทของด้าย ตัวล็อคที่ประตูเป็นแบบปิดโถส้วม: ช่วยให้มั่นใจว่าสามารถเข้าไปข้างในได้ครั้งละหนึ่งคนเท่านั้น กล่าวอีกนัยหนึ่ง ครั้งละหนึ่งเธรดเท่านั้นที่สามารถทำงานกับทรัพยากรที่ใช้ร่วมกันได้ ความพยายามของเธรดอื่น (บุคคล) ในการเข้าถึงทรัพยากรที่ถูกครอบครองจะล้มเหลว mutex มีคุณสมบัติที่สำคัญหลายประการ ประการแรกเป็นไปได้เพียงสองรัฐเท่านั้น - "ว่าง" และ "ไม่ว่าง" ซึ่งช่วยให้เข้าใจวิธีการทำงานได้ง่ายขึ้น: สามารถวาดแนวขนานด้วยตัวแปรบูลีนจริง/เท็จหรือระบบเลขฐานสอง 1/0 ประการที่สองรัฐไม่สามารถควบคุมได้โดยตรง ไม่มีกลไกใน Java ที่จะอนุญาตให้คุณรับอ็อบเจ็กต์ รับ mutex และกำหนดสถานะที่ต้องการให้กับอ็อบเจ็กต์ได้อย่างชัดเจน กล่าวอีกนัยหนึ่ง คุณไม่สามารถดำเนินการบางอย่าง เช่น:
Object myObject = new Object();
Mutex mutex = myObject.getMutex();
mutex.free();
ดังนั้นจึงไม่สามารถปล่อย mutex ของอ็อบเจ็กต์ได้ เฉพาะเครื่อง Java เท่านั้นที่สามารถเข้าถึงได้โดยตรง โปรแกรมเมอร์ทำงานกับ mutexes โดยใช้เครื่องมือภาษา
เฝ้าสังเกต
จอภาพเป็น “ส่วนเสริม” เพิ่มเติมสำหรับ mutex ในความเป็นจริง จอภาพเป็นเพียงส่วนหนึ่งของโค้ด ที่"มองไม่เห็น" สำหรับโปรแกรมเมอร์ เมื่อพูดถึง mutex ก่อนหน้านี้ เราได้ยกตัวอย่างง่ายๆ:public class Main {
private Object obj = new Object();
public void doSomething() {
//...some logic available to all threads
synchronized (obj) {
//logic that is only available to one thread at a time
}
}
}
ในบล็อกของโค้ดที่ทำเครื่องหมายด้วยคำว่าmutex synchronized
ของอ็อบเจ็กต์ของเราจะถูกบันทึก obj
โอเค การจับกุมเกิดขึ้น แต่ "กลไกการป้องกัน" บรรลุผลสำเร็จได้อย่างไร? synchronized
เหตุใด กระทู้อื่นจึงไม่สามารถเข้าไปในบล็อกได้เมื่อเห็นคำใดคำ หนึ่ง มันคือจอภาพที่สร้างกลไกการป้องกัน! คอมไพเลอร์จะแปลงคำดังกล่าวsynchronized
เป็นโค้ดพิเศษหลายชิ้น กลับมาที่ตัวอย่างของเราอีกครั้งพร้อมกับวิธีการdoSomething()
และเพิ่มเข้าไป:
public class Main {
private Object obj = new Object();
public void doSomething() {
//...some logic available to all threads
//logic that is only available to one thread at a time
synchronized (obj) {
/*выполнить важную работу, при которой доступ к an objectу
должен быть только у одного потока*/
obj.someImportantMethod();
}
}
}
นี่คือสิ่งที่จะเกิดขึ้น "ภายใต้ประทุน" ของโปรแกรมของเราหลังจากที่คอมไพเลอร์แปลงโค้ดนี้:
public class Main {
private Object obj = new Object();
public void doSomething() throws InterruptedException {
//...some logic available to all threads
//логика, которая одновременно доступна только для одного потока:
/*до тех пор, пока мьютекс an object занят -
любой другой поток (кроме того, который его захватил), спит*/
while (obj.getMutex().isBusy()) {
Thread.sleep(1);
}
//пометить мьютекс an object How занятый
obj.getMutex().isBusy() = true;
/*выполнить важную работу, при которой доступ к an objectу
должен быть только у одного потока*/
obj.someImportantMethod();
//освободить мьютекс an object
obj.getMutex().isBusy() = false;
}
}
ตัวอย่างนี้แน่นอนว่าไม่มีอยู่จริง ที่นี่ เมื่อใช้โค้ดที่คล้ายกับ Java เราพยายามสะท้อนถึงสิ่งที่เกิดขึ้นในขณะนี้ภายในเครื่อง Java อย่างไรก็ตาม รหัสเทียมนี้ให้ความเข้าใจที่ดีถึงสิ่งที่เกิดขึ้นจริงกับวัตถุและเธรดภายในบล็อกsynchronized
และวิธีที่คอมไพเลอร์แปลงคำนี้เป็นคำสั่งต่าง ๆ ที่โปรแกรมเมอร์ "มองไม่เห็น" โดยพื้นฐานแล้วจอภาพใน Java จะแสดงโดยใช้คำว่าsynchronized
. รหัสทั้งหมดที่ปรากฏแทนคำsynchronized
ในตัวอย่างสุดท้ายคือจอภาพ
สัญญาณ
อีกคำหนึ่งที่คุณเจอเมื่อศึกษามัลติเธรดด้วยตัวเองคือ "เซมาฟอร์" เรามาดูกันว่ามันคืออะไรและแตกต่างจากจอภาพและ mutex อย่างไร เซมาฟอร์เป็นวิธีการซิงโครไนซ์การเข้าถึงทรัพยากร ลักษณะเฉพาะของมันคือใช้ตัวนับเมื่อสร้างกลไกการซิงโครไนซ์ ตัวนับจะบอกเราว่าสามารถเข้าถึงทรัพยากรที่ใช้ร่วมกันได้พร้อมกันกี่เธรด Semaphores ใน Java แสดงโดยคลาสSemaphore
เมื่อสร้างวัตถุเซมาฟอร์ เราสามารถใช้ตัวสร้างต่อไปนี้:
Semaphore(int permits)
Semaphore(int permits, boolean fair)
เราส่งต่อไปยังตัวสร้าง:
-
int permits
— มูลค่าตัวนับเริ่มต้นและสูงสุด นั่นคือจำนวนเธรดที่สามารถเข้าถึงทรัพยากรที่ใช้ร่วมกันได้พร้อมกัน -
boolean fair
- เพื่อสร้างลำดับที่เธรดจะได้รับการเข้าถึง ถ้าfair
= trueให้สิทธิ์การเข้าถึงแก่เธรดที่รอตามลำดับที่พวกเขาร้องขอ หากเป็นเท็จลำดับจะถูกกำหนดโดยตัวกำหนดเวลาเธรด
class Philosopher extends Thread {
private Semaphore sem;
// поел ли философ
private boolean full = false;
private String name;
Philosopher(Semaphore sem, String name) {
this.sem=sem;
this.name=name;
}
public void run()
{
try
{
// если философ еще не ел
if (!full) {
//Запрашиваем у семафора разрешение на выполнение
sem.acquire();
System.out.println (name + " садится за стол");
// философ ест
sleep(300);
full = true;
System.out.println (name + " поел! Он выходит из-за стола");
sem.release();
// философ ушел, освободив место другим
sleep(300);
}
}
catch(InterruptedException e) {
System.out.println ("What-то пошло не так!");
}
}
}
และนี่คือโค้ดสำหรับรันโปรแกรมของเรา:
public class Main {
public static void main(String[] args) {
Semaphore sem = new Semaphore(2);
new Philosopher(sem,"Сократ").start();
new Philosopher(sem,"Платон").start();
new Philosopher(sem,"Аристотель").start();
new Philosopher(sem,"Фалес").start();
new Philosopher(sem,"Пифагор").start();
}
}
เราสร้างเซมาฟอร์โดยนับ 2 เพื่อตอบสนองเงื่อนไขที่นักปรัชญาเพียงสองคนเท่านั้นที่สามารถรับประทานอาหารพร้อมกันได้ นั่นคือมีเพียงสองเธรดเท่านั้นที่สามารถทำงานพร้อมกันได้ เนื่องจากคลาสของเราPhilosopher
สืบทอดมาจากThread
! คลาสacquire()
และวิธีการควบคุมตัวนับการอนุญาต วิธีการร้องขอสิทธิ์ในการเข้าถึงทรัพยากรจากเซมาฟอร์ หากตัวนับ > 0 สิทธิ์จะได้รับและตัวนับจะลดลง 1 วิธีการ"ปล่อย" สิทธิ์ที่ได้รับก่อนหน้านี้และส่งกลับไปยังตัวนับ (เพิ่มตัวนับการให้สิทธิ์ของเซมาฟอร์ด้วย 1) เราจะได้อะไรเมื่อเรารันโปรแกรม? ปัญหาได้รับการแก้ไขแล้วหรือยังนักปรัชญาของเราจะต่อสู้ในขณะที่รอถึงตาพวกเขาหรือไม่? :) นี่คือเอาต์พุตคอนโซลที่เราได้รับ: โสกราตีสนั่งลงที่โต๊ะ เพลโตนั่งลงที่โต๊ะ ที่โสกราตีสกิน! เขาออกจากโต๊ะ เพลโตกินแล้ว! เขาออกจากโต๊ะที่ อริสโตเติลนั่งลงที่โต๊ะที่พีธากอรัสนั่งลงที่โต๊ะที่ อริสโตเติลกิน! เขาออกจากโต๊ะที่พีทาโกรัสกินเข้าไปแล้ว! เขาออกจากโต๊ะ ที่ทาเลสนั่งลงที่โต๊ะ ที่ทาเลสกินเข้าไป! เขาออกจากโต๊ะแล้ว เราสำเร็จแล้ว! และแม้ว่าทาเลสจะต้องรับประทานอาหารคนเดียว ฉันคิดว่าเขาไม่ได้โกรธเรา :) คุณอาจสังเกตเห็นความคล้ายคลึงกันบางอย่างระหว่างมิวเท็กซ์และเซมาฟอร์ โดยทั่วไปมีวัตถุประสงค์เดียวกัน: เพื่อซิงโครไนซ์การเข้าถึงทรัพยากรบางอย่าง ข้อแตกต่างเพียงอย่างเดียวคือ mutex ของอ็อบเจ็กต์สามารถรับได้ครั้งละหนึ่งเธรดเท่านั้น ในขณะที่ในกรณีของเซมาฟอร์ จะใช้ตัวนับเธรด และหลายเธรดสามารถเข้าถึงทรัพยากรได้ในคราวเดียว และ นี่ไม่ใช่แค่ความคล้ายคลึงกันโดยบังเอิญ :) อันที่จริงmutex นั้นเป็นสัญญาณแบบจุดเดียว นั่นคือมันเป็นเซมาฟอร์ที่เริ่มตั้งตัวนับเป็น 1 เรียกอีกอย่างว่า "เซมาฟอร์ไบนารี" เพราะตัวนับสามารถมีค่าได้ 2 ค่าเท่านั้น - 1 (“ว่าง”) และ 0 (“ไม่ว่าง”) นั่นคือทั้งหมด! อย่างที่คุณเห็นทุกอย่างไม่สับสนมากนัก :) หากคุณต้องการศึกษาหัวข้อมัลติเธรดบนอินเทอร์เน็ตโดยละเอียดมากขึ้น คุณจะนำทางแนวคิดได้ง่ายขึ้นเล็กน้อย พบกันใหม่ในบทเรียนหน้า! release()
Semaphore
acquire()
release()
GO TO FULL VERSION