مرحبًا بك في درس مثال قفل جافا. عادةً ما نستخدم المتزامن لأمان الخيط عند العمل في بيئة متعددة الخيوط.
قفل جافا
في معظم الأحيان، الكلمة المفتاحية المتزامنة هي الطريقة المثلى ولكن لديها بعض النقائص التي فتحت الباب لاستخدام واجهة تطبيقات البرمجة للقفل في حزمة التسلسل الزمني لجافا. جافا 1.5 API للتسلسل الزمني جاءت مع
java.util.concurrent.locks
حزمة مع واجهة Lock
وبعض فئات التنفيذ لتحسين آلية قفل الكائن. بعض الواجهات والفئات الهامة في واجهة تطبيقات البرمجة للقفل في جافا هي:
-
Lock: هذه هي الواجهة الأساسية لواجهة قفل الكائن. توفر جميع ميزات الكلمة المفتاحية المتزامنة بطرق إضافية لإنشاء شروط مختلفة للقفل، وتوفير فترة انتظار للخيط للحصول على القفل. بعض الأساليب الهامة هي lock() لاستحصال القفل، unlock() لإطلاق القفل، tryLock() لانتظار القفل لفترة معينة من الوقت، newCondition() لإنشاء الشرط الخ.
-
الشرط: تشابه كائنات الشرط مع نموذج انتظار-إعلام الكائن بإمكانية إنشاء مجموعات مختلفة من الانتظار. يتم إنشاء كائن الشرط دائمًا بواسطة كائن القفل. بعض الطرق الهامة هي await() وهي مشابهة ل wait()، و signal()، signalAll() وهي مشابهة للأساليب notify() و notifyAll().
-
القفل قراءة/كتابة: يحتوي على زوج من القفلين المرتبطين، واحد لعمليات القراءة فقط والآخر للكتابة. يمكن أن يتم الاحتفاظ بالقفل القراءة بشكل متزامن من قبل خيوط القارئ المتعددة طالما لا توجد خيوط كتابة. القفل الكتابة حصري.
-
ReentrantLock: هذه هي أكثر فئة تنفيذ استخدامًا لواجهة القفل. تقوم هذه الفئة بتنفيذ واجهة القفل بنفس الطريقة كمفتاح synchronized. بالإضافة إلى تنفيذ واجهة القفل ، تحتوي ReentrantLock على بعض الطرق المفيدة للحصول على الخيط الذي يحمل القفل ، والخيوط التي تنتظر للحصول على القفل ، إلخ. يعتبر كتل synchronized إعادة تنفيذًا في الطبيعة ، أي إذا كان لديك خيط قد قفل على كائن المراقبة وإذا كان كتلة synchronized أخرى تتطلب قفلًا على نفس كائن المراقبة ، يمكن للخيط دخول تلك الكتلة الكود. أعتقد أن هذا هو السبب في أن اسم الفئة هو ReentrantLock. دعونا نفهم هذه الميزة من خلال مثال بسيط.
public class Test{ public synchronized foo(){ // قم بعمل ما ما bar(); } public synchronized bar(){ // افعل بعض المزيد } }
إذا دخل خيط في foo() ، فإنه يحمل قفل على كائن Test ، لذلك عند محاولته تنفيذ طريقة bar() ، يُسمح للخيط بتنفيذ طريقة bar() لأنه بالفعل يحمل القفل على كائن Test ، أي نفس synchronized(this).
مثال قفل جافا – ReentrantLock في جافا
الآن دعونا نرى مثالًا بسيطًا حيث سنستبدل كلمة synchronized بواجهة قفل جافا. لنفترض أن لدينا فئة Resource مع بعض العمليات التي نريد أن تكون آمنة للخيوط وبعض الطرق حيث لا يُطلب توفير سلامة الخيوط.
package com.journaldev.threads.lock;
public class Resource {
public void doSomething(){
//افعل بعض العمليات، قراءة قاعدة البيانات، كتابة، إلخ
}
public void doLogging(){
//تسجيل، ليس هناك حاجة لسلامة الخيوط
}
}
الآن لنفترض أن لدينا فئة Runnable حيث سنستخدم أساليب الموارد.
package com.journaldev.threads.lock;
public class SynchronizedLockExample implements Runnable{
private Resource resource;
public SynchronizedLockExample(Resource r){
this.resource = r;
}
@Override
public void run() {
synchronized (resource) {
resource.doSomething();
}
resource.doLogging();
}
}
لاحظ أنني أستخدم كتلة متزامنة للحصول على قفل على كائن المورد. يمكننا أن نقوم بإنشاء كائن وهمي في الفئة واستخدامه لأغراض القفل. الآن دعنا نرى كيف يمكننا استخدام واجهة قفل جافا وإعادة كتابة البرنامج أعلاه بدون استخدام الكلمة المفتاحية synchronized. سنستخدم ReentrantLock في جافا.
package com.journaldev.threads.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConcurrencyLockExample implements Runnable{
private Resource resource;
private Lock lock;
public ConcurrencyLockExample(Resource r){
this.resource = r;
this.lock = new ReentrantLock();
}
@Override
public void run() {
try {
if(lock.tryLock(10, TimeUnit.SECONDS)){
resource.doSomething();
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
// إطلاق القفل
lock.unlock();
}
resource.doLogging();
}
}
كما يمكنك أن ترى ، أستخدم طريقة tryLock () للتأكد من أن الخيط الخاص بي ينتظر فقط لفترة زمنية محددة وإذا لم يحصل على القفل على الكائن ، فإنه يقوم بتسجيل الدخول والخروج فقط. نقطة مهمة أخرى للاحتراز هي استخدام كتلة try-finally للتأكد من أن القفل مفكك حتى إذا كان استدعاء طريقة doSomething () يلقي أي استثناء.
Java Lock مقابل synchronized
نستنتج بسهولة الاختلافات التالية بناءً على التفاصيل والبرنامج أعلاه بين قفل جافا والتزامن.
- توفر واجهة قفل جافا مزيدًا من الرؤية والخيارات للقفل ، على عكس synchronized حيث يمكن أن ينتهي الخيط في الانتظار إلى أجل غير مسمى للقفل ، يمكننا استخدام tryLock () للتأكد من أن الخيط ينتظر لفترة زمنية محددة فقط.
- رمز التزامن أكثر نظافة وسهولة في الصيانة ، في حين أنه مع قفل ، نُجبر على وجود كتلة try-finally للتأكد من أن القفل مفكك حتى إذا كانت هناك أي استثناء يتم إلقاؤه بين استدعاءات طريقة lock () و unlock ().
- تستطيع كتل المزامنة أو الأساليب تغطية طريقة واحدة فقط، في حين يمكننا الحصول على القفل في طريقة وتحريره في طريقة أخرى باستخدام واجهة برمجة التطبيقات للقفل.
- الكلمة المفتاحية synchronized لا توفر العدالة في حين يمكننا تعيين العدالة إلى صحيح أثناء إنشاء كائن ReentrantLock بحيث يحصل أطول خيط انتظار على القفل أولاً.
- يمكننا إنشاء شروط مختلفة للقفل ويمكن لمواضيع مختلفة الانتظار باستخدام await() لظروف مختلفة.
هذا كل شيء بالنسبة لمثال قفل Java، ReentrantLock في Java وتحليل مقارنة مع الكلمة المفتاحية synchronized.
Source:
https://www.digitalocean.com/community/tutorials/java-lock-example-reentrantlock