التزامن في جافا

صُممت لغة برمجة جافا وآلة جافا الافتراضية (JVM) لدعم البرمجة المتزامنة . يتم تنفيذ جميع العمليات ضمن سياق الخيوط . يمكن الوصول إلى الكائنات والموارد بواسطة العديد من الخيوط المنفصلة، ​​حيث يمتلك كل خيط مسار تنفيذ خاص به، مع إمكانية الوصول إلى أي كائن في البرنامج. يجب تنسيق عمليات القراءة والكتابة للكائنات بشكل صحيح (أو " مزامنتها ") بين الخيوط. [ 1 ] [ 2 ] تضمن مزامنة الخيوط تعديل الكائنات بواسطة خيط واحد فقط في كل مرة، وتمنع الخيوط من الوصول إلى الكائنات التي تم تحديثها جزئيًا أثناء تعديلها بواسطة خيط آخر. [ 2 ]

العمليات والخيوط

تعمل معظم تطبيقات آلة جافا الافتراضية كعملية واحدة . في لغة برمجة جافا، يهتم البرمجة المتزامنة بشكل أساسي بالخيوط (وتسمى أيضًا العمليات الخفيفة ). لا يمكن تحقيق عمليات متعددة إلا باستخدام آلات جافا افتراضية متعددة.

كائنات الخيوط

تتشارك الخيوط موارد العملية، بما في ذلك الذاكرة والملفات المفتوحة. وهذا يُسهّل التواصل، ولكنه قد يُسبب بعض المشاكل. [ 2 ] يحتوي كل تطبيق على خيط واحد على الأقل يُسمى الخيط الرئيسي. يمتلك الخيط الرئيسي القدرة على إنشاء خيوط إضافية Runnableككائنات Callable. java.lang.Callableتتشابه واجهة هذه الكائنات مع واجهة java.lang.Runnableأخرى، حيث صُممت كلتاهما لفئات يُمكن تنفيذ مثيلاتها بواسطة خيط آخر. [ 3 ]java.lang.Runnable مع ذلك، لا تُرجع هذه الكائنات نتيجة ولا يُمكنها طرح استثناء مُدقّق . [ 4 ]

يمكن جدولة كل خيط [ 5 ] على نواة معالجة مركزية مختلفة [ 6 ] ، أو استخدام تقنية تقسيم الوقت على معالج واحد، أو على عدة معالجات. لا يوجد حل عام لكيفية ربط خيوط جافا بخيوط نظام التشغيل الأصلية، إذ يمكن لكل تطبيق لآلة جافا الافتراضية (JVM) القيام بذلك بطريقة مختلفة.

يرتبط كل خيط بنسخة من الفئة java.lang.Thread. يمكن إدارة الخيوط إما مباشرةً باستخدام java.lang.Threadالكائنات، أو بشكل غير مباشر باستخدام آليات مجردة مثل java.util.concurrent.Executorفئات المهام. [ 7 ] توفر لغة جافا فئات المهام التالية:

  • java.util.concurrent.ForkJoinTask<V>وذريتها:
    • java.util.concurrent.CountedCompleter<T>
    • java.util.concurrent.RecursiveAction(يكون ForkJoinTask<Void>)
    • java.util.concurrent.RecursiveTask<V>
  • java.util.concurrent.FutureTask<V>
    • javafx.concurrent.Task<V>(من JavaFX )

كجزء من مشروع Loom في Java 21، تم تقديم الخيوط الافتراضية ، التي تسمح بتنفيذ تعليمات برمجية متزامنة، مع إمكانية التوسع لتشمل ملايين المهام. [ 8 ] [ 9 ] يُطلق على خيط Java التقليدي اسم "خيط النظام الأساسي"، وهو ما يُشابه تقريبًا خيط نظام التشغيل، لكن خيوط Java الافتراضية خفيفة الوزن وتُدار بواسطة JVM، وتُجدول على مجموعة صغيرة من خيوط نظام التشغيل الحقيقية. لا تزال الخيوط الافتراضية متزامنة java.lang.Thread، ولكن يتم إنشاؤها باستخدام طرق المصنع ofVirtual(). على الرغم من أن التعليمات البرمجية لا تزال متزامنة، فإن خيوط نظام التشغيل الفعلية غير متزامنة؛ فعند التوقف للانتظار، يفصل JVM التعليمات البرمجية عن الخيط الفعلي، ويُشغل مهامًا أخرى حتى يُرسل نظام التشغيل إشعارًا بحدث غير متزامن لاستئناف تنفيذ التعليمات البرمجية من حيث توقفت.

بدء الموضوع

يمكن بدء سلسلة العمليات من خلال توفير java.lang.Runnableكائن:

public class HelloRunnable implements Runnable { @Override public void run () { System . out . println ( "Hello from thread!" ); }public static void main ( String [] args ) { Thread t = new Thread ( new HelloRunnable ()); t . start (); } }

أو من خلال التقسيم الفرعي:

public class HelloThread extends Thread { @Override public void run () { System . out . println ( "Hello from thread!" ); }public static void main ( String [] args ) { HelloThread t = new HelloThread (); t . start (); } }

المقاطعات

يُخبر المقاطعةُ الخيطَ بضرورةِ التوقفِ عمّا يقوم به والقيامِ بشيءٍ آخر. يُرسلُ الخيطُ مقاطعةً باستدعاءِ دالةِ المقاطعةِ interrupt()على java.lang.Threadالخيطِ المراد مقاطعته. تُنفَّذُ آليةُ المقاطعةِ باستخدامِ علامةٍ داخليةٍ تُشيرُ إلى حالةِ المقاطعة، والتي تُعيَّنُ عندَ استدعاءِ دالةِ المقاطعة interrupt(). [ 10 ] [ 11 ] أيُّ دالةٍ تُنهي عملها بإطلاقِ استثناءٍ تُزيلُ java.lang.InterruptedExceptionحالةَ المقاطعةِ عندَ قيامِها بذلك، ولكن هناك احتمالٌ أن تُعيَّنَ حالةُ المقاطعةِ مرةً أخرى فورًا، بواسطةِ خيطٍ آخرَ يستدعي دالة المقاطعة interrupt().

تاريخيًا، كان بالإمكان إيقاف الخيوط فورًا باستخدام هذه stop()الطريقة؛ عند استدعاء هذه الطريقة، java.lang.ThreadDeathكان يتم طرح استثناء، مما يؤدي إلى مسح الخيط. كانت هذه الطريقة خطيرة لأنها قد تترك البيانات في حالة تالفة، وتمت إزالتها في النهاية في Java 20. [ 12 ]

ينضم

java.lang.Thread#join()تتيح هذه الطريقة لشخص ما java.lang.Threadالانتظار حتى يكتمل شخص آخر.

الاستثناءات

ستؤدي الاستثناءات غير المعالجة التي يطرحها الكود إلى إنهاء الخيط. يقوم الخيط الرئيسي بطباعة الاستثناءات على وحدة التحكم، ولكن الخيوط التي أنشأها المستخدم تحتاج إلى معالج مسجل للقيام بذلك. [ 13 ] [ 14 ]

نموذج الذاكرة

يصف نموذج ذاكرة جافا كيفية تفاعل الخيوط في لغة برمجة جافا عبر الذاكرة. في المنصات الحديثة، لا يُنفَّذ الكود غالبًا بالترتيب الذي كُتب به، بل يُعاد ترتيبه بواسطة المُصرِّف والمعالج ونظام الذاكرة الفرعي لأسباب تتعلق بالأداء. لا يوجد ضمان للتسلسل الخطي ، أو حتى الاتساق التسلسلي ، [ 15 ] عند قراءة أو كتابة حقول الكائنات المشتركة. يسمح هذا بتحسينات المُصرِّف (مثل تخصيص السجلات ، وحذف التعبيرات الفرعية المشتركة ، وحذف عمليات القراءة الزائدة )، من خلال إعادة ترتيب عمليات قراءة وكتابة الذاكرة. [ 16 ]

التزامن

تتواصل الخيوط بشكل أساسي من خلال مشاركة الوصول إلى الحقول والكائنات التي تشير إليها هذه الحقول. هذا النوع من التواصل فعال للغاية، ولكنه يُتيح حدوث نوعين من الأخطاء: تداخل الخيوط وأخطاء اتساق الذاكرة.

قد تحدث عمليات إعادة الترتيب في البرامج متعددة الخيوط المتزامنة بشكل غير صحيح ، حيث يكون أحد الخيوط قادرًا على ملاحظة تأثيرات الخيوط الأخرى، وقد يكون قادرًا على اكتشاف أن عمليات الوصول إلى المتغيرات تصبح مرئية للخيوط الأخرى بترتيب مختلف عن الترتيب الذي تم تنفيذه أو تحديده في البرنامج.

عندما يتعين على الخيوط التفاعل مع بعضها البعض، يُستخدم التزامن. ولتزامن الخيوط، تستخدم جافا المراقبين ، وهي آلية عالية المستوى تسمح لخيط واحد فقط في كل مرة بتنفيذ جزء من التعليمات البرمجية المحمية بواسطة المراقب. يُشرح سلوك المراقبين من حيث الأقفال ؛ حيث يوجد قفل مرتبط بكل كائن.

للتزامن العديد من التطبيقات. ومن أبرزها الاستبعاد المتبادل ، حيث لا يمكن إلا لخيط واحد الاحتفاظ بمراقب في وقت واحد، لذا فإن التزامن على مراقب يعني أنه بمجرد دخول خيط واحد إلى كتلة متزامنة محمية بواسطة مراقب، لا يمكن لأي خيط آخر دخول كتلة محمية بواسطة ذلك المراقب حتى يخرج الخيط الأول من الكتلة المتزامنة. [ 2 ]

تضمن المزامنة أن تكون عمليات الكتابة في الذاكرة التي يقوم بها أحد الخيوط قبل أو أثناء تنفيذ كتلة متزامنة مرئيةً بطريقة متوقعة للخيوط الأخرى التي تتزامن على نفس الشاشة. بمجرد synchronizedاكتمال الكتلة، يتم تحرير الشاشة، مما يؤدي إلى تفريغ ذاكرة التخزين المؤقت إلى الذاكرة الرئيسية، بحيث تصبح عمليات الكتابة التي يقوم بها هذا الخيط مرئيةً للخيوط الأخرى. قبل synchronizedبدء تنفيذ كتلة جديدة، يتم الحصول على الشاشة، مما يؤدي إلى إبطال ذاكرة التخزين المؤقت للمعالج المحلي، وبالتالي إعادة تحميل المتغيرات من الذاكرة الرئيسية. في تلك اللحظة، تصبح جميع عمليات الكتابة التي تم إجراؤها في عملية التحرير السابقة مرئية.

تكون عمليات القراءة والكتابة للحقول قابلة للتسلسل الخطي إذا كان الحقل متقلبًا ، أو إذا كان الحقل محميًا بقفل فريد يتم الحصول عليه من قبل جميع القراء والكتاب.

الأقفال والكتل المتزامنة

يمكن للخيط تحقيق الاستبعاد المتبادل إما بالدخول إلى كتلة أو دالة متزامنة، والتي تحصل على قفل ضمني، [ 17 ] [ 2 ] أو بالحصول على قفل صريح (مثل القفل ReentrantLockالموجود في java.util.concurrent.locksالحزمة [ 18 ] ). كلا النهجين لهما نفس الآثار على سلوك الذاكرة. إذا كانت جميع عمليات الوصول إلى حقل معين محمية بنفس القفل، فإن عمليات القراءة والكتابة إلى ذلك الحقل تكون خطية (ذرية).

قد تقوم آلة جافا الافتراضية (JVM) أيضاً بتطبيق القفل أثناء التشغيل: يُعرف هذا باسم "تقنية القفل المتحيز بدون تخزين" أو "القفل المتحيز". [ 19 ]

الحقول المتقلبة

عند تطبيق الكلمة المفتاحية على حقل، volatileفإنها تضمن وجود ترتيب عالمي لعمليات القراءة والكتابة في volatileمتغير. وهذا يعني أن كل عملية تصل إلى volatileحقل ستقرأ قيمته الحالية قبل المتابعة، بدلاً من استخدام قيمة مخزنة مؤقتًا (إن أمكن). مع ذلك، لا يوجد ضمان بشأن الترتيب النسبي لعمليات القراءة والكتابة المؤقتة مع عمليات القراءة والكتابة العادية.

منذ إصدار Java 5، volatileتُنشئ عمليات القراءة والكتابة علاقة "يحدث قبل" ، تمامًا مثل الحصول على قفل التزامن وتحريره. [ 20 ] هذه العلاقة ببساطة هي ضمان أن عمليات الكتابة في الذاكرة التي تُنفذها عبارة محددة تكون مرئية لعبارة محددة أخرى.

volatileالحقول قابلة للتسلسل. قراءة volatileحقل ما تشبه الحصول على قفل: يتم إبطال ذاكرة العمل، volatileوتُعاد قراءة القيمة الحالية للحقل من الذاكرة. كتابة volatileحقل ما تشبه تحرير القفل: volatileيُكتب الحقل فورًا إلى الذاكرة.

الحقول النهائية

لا يمكن تعديل الحقل المُعلن عنه finalبعد تهيئته. [ 21 ] تُهيأ حقول الكائن finalفي مُنشئه. وطالما thisلم يُحرر المرجع من المُنشئ قبل انتهاء المُنشئ، finalستكون القيمة الصحيحة لأي حقل مرئية للخيوط الأخرى دون الحاجة إلى تزامن. [ 22 ]

الذرات والأقفال

توفر لغة جافا ميزات الذريةjava.util.concurrent.atomic في . توفر هذه الميزات أنواعًا خالية من الأقفال وآمنة للخيوط مع عمليات ذرية، مع تجنب synchronized.

للأنواع الأولية ومصفوفات الأنواع الأولية أنواعها الذرية الخاصة (مثل java.util.concurrent.atomic.AtomicIntegerو )، وبالنسبة لأنواع المراجع، يتم توفير java.util.concurrent.atomic.AtomicIntegerArrayالفئة .java.util.concurrent.atomic.AtomicReference<T>

استيراد java.util.concurrent.atomic.AtomicReference ؛AtomicReference <String> ref = new AtomicReference <> ( "Hello..." ) ; ref.set ( " ... world!" ) ; boolean success = ref.compareAndSet ( " ...world!" , " Hello, world!" ) ; System.out.println ( ref.get ( ) ) ; // "Hello, world ! "

العقود الآجلة

بدلاً من ذلك ، يتم توفير java.lang.Threadفئة أخرى للحساب غير المتزامن [ 23 ] ؛ يمكن اعتبار هذا مشابهًا لـ في C# ، على الرغم من أن Java تفتقر إلى الروتينات الفرعية الصريحة مثل تلك الموجودة في C#.java.util.concurrent.CompletableFuture<T>System.Threading.Tasks.Task<T>

تاريخ

منذ إصدار JDK 1.2 ، أضافت لغة جافا مجموعة قياسية من فئات المجموعات، وهي إطار عمل مجموعات جافا.

قام دوغ ليا ، الذي شارك أيضًا في تنفيذ إطار عمل مجموعات جافا، بتطوير حزمة تزامنية ، تضم العديد من الأدوات الأساسية للتزامن ومجموعة كبيرة من الفئات المتعلقة بالمجموعات. [ 24 ] استمر هذا العمل وتم تحديثه كجزء من JSR 166 الذي ترأسه دوغ ليا.

أضافت JDK 5.0 العديد من التحسينات والتوضيحات لنموذج التزامن في جافا. كما تم تضمين واجهات برمجة تطبيقات التزامن التي طورتها JSR 166 كجزء من JDK لأول مرة. ووفرت JSR 133 دعمًا للعمليات الذرية المحددة جيدًا في بيئة متعددة الخيوط/متعددة المعالجات.

قدم كل من إصداري Java SE 6 و Java SE 7 إصدارات محدثة من واجهات برمجة تطبيقات JSR 166 بالإضافة إلى العديد من واجهات برمجة التطبيقات الإضافية الجديدة.

انظر أيضاً

ملحوظات

  1. Goetz et al. 2006 ، ص 15-17 ، §2 سلامة الخيوط.
  2. 1 2 3 4 5 Bloch 2018 ، ص 126-129 ، الفصل §11 البند 78: مزامنة الوصول إلى البيانات القابلة للتغيير المشتركة.
  3. Goetz et al. 2006 ، ص 125-126 ، §6.3.2 المهام ذات النتائج: القابلة للاستدعاء والمستقبلية.
  4. Goetz et al. 2006 ، ص 95-98 ، §5.5.2 FutureTask.
  5. بلوخ 2018 ، الصفحات 336-337، الفصل §11 البند 84 لا تعتمد على جدولة الخيوط.
  6. بلوخ 2018 ، ص 311، الفصل §11 التزامن.
  7. بلوخ 2018 ، ص 323-324، الفصل §5 البند 80: تفضيل المنفذين والمهام والتدفقات على الخيوط.
  8. "الخيوط الافتراضية" . مركز مساعدة أوراكل . تم الاطلاع عليه بتاريخ 10-09-2024 .
  9. "JEP 491: مزامنة الخيوط الافتراضية بدون تثبيت" . OpenJDK . تم الاسترجاع في 30-03-2025 .
  10. Goetz et al. 2006 ، ص 138-141 ، §7.1.1 الانقطاع.
  11. Goetz et al. 2006 ، ص 92-94 ، §5.4 طرق الحظر والمقاطعة.
  12. OpenJDK (23 سبتمبر 2025). "ملاحظة الإصدار: تمت إزالة Thread.stop" . bugs.openjdk.org . OpenJDK.
  13. أوراكل. "واجهة Thread.UncaughtExceptionHandler" . تم الاطلاع عليه بتاريخ 10 مايو 2014 .
  14. "موت الخيط الصامت بسبب استثناءات غير معالجة" . literatejava.com . 10 مايو 2014. تم الاطلاع عليه في 10 مايو 2014 .
  15. Goetz et al. 2006 ، ص 338-339 ، §16.1.1 نماذج ذاكرة المنصة.
  16. هيرليهي، موريس، ونير شافيت. "فن برمجة المعالجات المتعددة". PODC. المجلد 6. 2006.
  17. Goetz et al. 2006 ، ص 25-26 ، §2.3.1 الأقفال الجوهرية.
  18. Goetz et al. 2006 ، ص 277-278 ، §13 الأقفال الصريحة.
  19. "المزامنة وقفل الكائنات" .
  20. القسم 17.4.4: ترتيب التزامن "مواصفات لغة جافا®، إصدار جافا SE 7" . شركة أوراكل . 2013. تاريخ الاسترجاع: 12 مايو 2013 .
  21. Goetz et al. 2006 ، ص 48 ، §3.4.1 الحقول النهائية.
  22. Goetz et al. 2006 ، ص 41-42 ، §3.2.1 ممارسات البناء الآمنة.
  23. شركة أوراكل. "فئة CompletableFuture<T>" . docs.oracle.com . شركة أوراكل . تم الاطلاع عليه بتاريخ 27 مايو 2026 .
  24. دوغ ليا . "نظرة عامة على حزمة util.concurrent الإصدار 1.3.4" . تم الاطلاع عليه بتاريخ 1 يناير 2011. ملاحظة: عند إصدار J2SE 5.0، ستدخل هذه الحزمة في وضع الصيانة: سيتم إصدار التصحيحات الأساسية فقط. تتضمن حزمة java.util.concurrent الخاصة بـ J2SE5 إصدارات محسّنة وأكثر كفاءة وموحدة من المكونات الرئيسية في هذه الحزمة.

فهرس