مرحبًا! أثناء دراسة تعدد مؤشرات الترابط في JavaRush، كثيرًا ما صادفت مفاهيم "mutex" و"monitor". هل يمكنك الآن، دون إلقاء نظرة خاطفة، الإجابة على مدى اختلافهما؟ :) إذا استطعت، أحسنت! إذا لم يكن الأمر كذلك (ويحدث هذا غالبًا) - فلا عجب. إن مفهومي "mutex" و"الشاشة" مرتبطان بالفعل. علاوة على ذلك، أثناء قراءة المحاضرات ومشاهدة مقاطع الفيديو حول تعدد مؤشرات الترابط على الموارد الخارجية على الإنترنت، سوف تصادف مفهومًا آخر مشابهًا - "الإشارة". وظيفتها أيضًا تشبه إلى حد كبير الشاشة وكائن المزامنة (mutex). لذلك، دعونا نفهم هذه المصطلحات الثلاثة، وننظر إلى بعض الأمثلة وننظم أخيرًا في رؤوسنا فهم مدى اختلافها عن بعضها البعض :)
سنقوم بتبسيط مصطلحاته قليلاً لفهم أفضل. تخيل أن لدينا 5 فلاسفة يحتاجون إلى الغداء. وفي الوقت نفسه، لدينا طاولة واحدة، ولا يمكن أن يجلس عليها أكثر من شخصين في نفس الوقت. مهمتنا هي إطعام جميع الفلاسفة. لا ينبغي لأي منهما أن يجوع، ولا ينبغي أن "يعترضا" بعضهما البعض عندما يحاولان الجلوس على الطاولة (يجب علينا تجنب الطريق المسدود). وهذا ما سيبدو عليه صفنا الفيلسوف:
موتيكس
كائن المزامنة (mutex) هو كائن خاص لمزامنة سلاسل الرسائل. إنه "مرتبط" بكل كائن في Java - أنت تعرف ذلك بالفعل :) لا يهم ما إذا كنت تستخدم الفئات القياسية أو أنشأت فئاتك الخاصة، على سبيل المثال،Cat
و Dog
: جميع الكائنات من جميع الفئات لها كائن المزامنة (mutex) . يأتي اسم "mutex" من الكلمة الإنجليزية "MUTual Exclusion" - "الاستبعاد المتبادل"، وهذا يعكس الغرض منه تمامًا. كما قلنا في إحدى المحاضرات السابقة، فإن مهمة كائن المزامنة (mutex) هي توفير مثل هذه الآلية بحيث يتمكن مؤشر ترابط واحد فقط من الوصول إلى كائن ما في وقت معين . التشبيه الشائع لكائن المزامنة في الحياة الواقعية هو "مثال المرحاض". عندما يدخل الإنسان إلى المرحاض، فإنه يقفل الباب من الداخل. يعمل المرحاض ككائن يمكن الوصول إليه عن طريق خيوط متعددة. القفل الموجود على باب المرحاض هو دور كائن المزامنة (mutex)، وطابور الأشخاص بالخارج هو دور الخيوط. القفل الموجود على الباب هو عبارة عن مرحاض: فهو يضمن تواجد شخص واحد فقط بالداخل في كل مرة. بمعنى آخر، يمكن لمؤشر ترابط واحد فقط في كل مرة العمل على الموارد المشتركة. ستفشل محاولات المواضيع الأخرى (الأشخاص) للوصول إلى الموارد المشغولة. يحتوي كائن المزامنة (mutex) على العديد من الميزات المهمة. أولاً ، هناك حالتان فقط ممكنتان - "مجاني" و"مشغول". وهذا يجعل من السهل فهم كيفية عمله: يمكن رسم المتوازيات باستخدام المتغيرات المنطقية صحيح/خطأ أو نظام الأرقام الثنائية 1/0. ثانياً ، لا يمكن السيطرة على الدول بشكل مباشر. لا توجد آليات في Java تسمح لك بأخذ كائن بشكل صريح والحصول على كائن المزامنة (mutex) الخاص به وتعيين الحالة المطلوبة له. بمعنى آخر، لا يمكنك فعل شيء مثل:
Object myObject = new Object();
Mutex mutex = myObject.getMutex();
mutex.free();
وبالتالي، لا يمكن تحرير كائن المزامنة (mutex) للكائن. فقط جهاز Java لديه حق الوصول المباشر إليه. يعمل المبرمجون مع كائنات المزامنة باستخدام أدوات اللغة.
شاشة
الشاشة عبارة عن "وظيفة إضافية" إضافية لكائن المزامنة (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
}
}
}
في كتلة التعليمات البرمجية المميزة بالكلمة synchronized
، يتم التقاط كائن المزامنة (mutex) الخاص بالكائن الخاص بنا 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). الإشارة هي وسيلة لمزامنة الوصول إلى المورد. تكمن خصوصيته في أنه يستخدم عدادًا عند إنشاء آلية المزامنة. يخبرنا العداد بعدد سلاسل العمليات التي يمكنها الوصول إلى مورد مشترك في نفس الوقت. يتم تمثيل الإشارات في 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()
تتحكم release()
الفصل والأساليب Semaphore
في عداد الأذونات الخاص به. تطلب الطريقة acquire()
إذنًا للوصول إلى مورد من الإشارة. إذا كان العداد > 0، يتم منح الإذن وتقليل العداد بمقدار 1. release()
"تحرر" الطريقة الإذن الممنوح مسبقًا وتعيده إلى العداد (زيادة عداد منح الإشارة بمقدار 1). ماذا نحصل عندما نقوم بتشغيل البرنامج؟ فهل تم حل المشكلة، وهل سيتقاتل فلاسفتنا وهم ينتظرون دورهم؟ :) هذا هو مخرج وحدة التحكم الذي تلقيناه: سقراط يجلس على الطاولة، أفلاطون يجلس على الطاولة التي أكلها سقراط! يترك الطاولة وقد أكل أفلاطون! يترك المائدة ويجلس أرسطو على المائدة فيثاغورس يجلس على المائدة التي أكلها أرسطو! يترك المائدة التي أكلها فيثاغورس! يترك الطاولة ويجلس طاليس على الطاولة التي أكلها طاليس! لقد ترك الطاولة لقد نجحنا! وعلى الرغم من أن طاليس كان عليه أن يتناول العشاء بمفرده، إلا أنني أعتقد أنه ليس غاضبًا منا :) ربما لاحظت بعض أوجه التشابه بين كائن المزامنة (mutex) والإشارة المرورية. بشكل عام، لديهم نفس الغرض: مزامنة الوصول إلى بعض الموارد. والفرق الوحيد هو أنه لا يمكن الحصول على كائن المزامنة (mutex) الخاص بالكائن إلا من خلال مؤشر ترابط واحد في كل مرة، بينما في حالة الإشارة، يتم استخدام عداد الخيوط، ويمكن للعديد منها الوصول إلى المورد في وقت واحد. وهذا ليس مجرد تشابه من قبيل الصدفة :) في الواقع، كائن المزامنة عبارة عن إشارة ذات مكان واحد . أي أنها إشارة تم ضبط عدادها في البداية على 1. وتسمى أيضًا "الإشارة الثنائية" لأن عدادها يمكن أن يحتوي على قيمتين فقط - 1 ("مجاني") و0 ("مشغول"). هذا كل شئ! كما ترون، فقد تبين أن كل شيء لم يكن مربكًا للغاية :) الآن، إذا كنت ترغب في دراسة موضوع تعدد العمليات بمزيد من التفصيل على الإنترنت، فسيكون من الأسهل عليك التنقل بين المفاهيم. نراكم في الدروس القادمة!
GO TO FULL VERSION