البرمجة

تجنب تداخلات السباق في Java باستخدام synchronize

في هذا الكود، يتم استخدام الصف A الذي ينفذ واجهة ActionListener ويحتوي على متغير ستاتيك X. تقوم الصف B بتمديد الفئة Thread وتقوم بزيادة قيمة X بعد فترة زمنية محددة. عند تشغيل البرنامج، يتم إنشاء كائن من الصف A، وبالتالي يتم إنشاء كائن من الصف B الذي يزيد قيمة X.

لكن، هنا يكمن السبب في طباعة القيمة 1 دائمًا. السبب يعود إلى أن الكائنات من الصف A يتم إنشاؤها بشكل متزامن في نفس الوقت، وعندما يقوم الكائن من الصف B بزيادة قيمة X، يمكن أن تحدث تداخلات (race conditions) مما يؤدي إلى نتائج غير متوقعة.

لفهم السبب، عندما يبدأ البرنامج، يتم تشغيل الكائنات من الصف A والصف B في نفس الوقت. قد يحدث أن يتم تشغيل الصف B قبل الانتهاء من تشغيل الصف A. في هذه الحالة، سيتم زيادة قيمة X من قبل الصف B بينما يقوم الصف A بطباعة قيمته، وهي دائمًا 0، مما يفسر الطباعة المستمرة للقيمة 1.

يمكن حل هذه المشكلة باستخدام synchronize لضمان أن الزيادة في قيمة X تتم بشكل آمن، وبالتالي يتم التحكم في تداخلات السباق. يمكن تعديل الكود كما يلي:

java
public class A implements ActionListener{ public static int X = 0; private Timer timer; public A(){ timer = new Timer(16, this); timer.setInitialDelay(16); timer.start(); } public synchronized void actionPerformed(ActionEvent arg0) { System.out.println(X); } public static void main(String args[]){ new A(); new B().start(); } } class B extends Thread { public void run(){ try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (A.class) { A.X++; } } }

باستخدام synchronize، يتم ضمان أن تتم عملية الزيادة في قيمة X بشكل آمن، وبالتالي يمكن تفادي مشكلة تداخلات السباق والحصول على النتائج المتوقعة.

المزيد من المعلومات

بالطبع، دعنا نوسع قليلاً على الشرح ونقدم بعض المعلومات الإضافية حول الكود المقدم. يتعامل الكود بشكل رئيسي مع مفهومات هامة في برمجة Java، مثل واجهات ActionListener وThread.

أولًا، يُلاحظ أن الصف A ينفذ واجهة ActionListener. هذا يعني أنه يقوم بتعريف طريقة actionPerformed التي يتم استدعاؤها عند حدوث حدث يتم رصده بواسطة هذا المستمع. في هذه الحالة، يتم استدعاء actionPerformed بانتظام بفعل الكائن Timer كل 16 مللي ثانية.

عند التنفيذ، يبدأ الكائن A بتشغيل Timer الذي يستدعي actionPerformed بانتظام. وهنا يأتي السؤال: لماذا يظل الإخراج ثابتًا عند 1 دائمًا؟

الإجابة تعود إلى تداخلات السباق (Race Conditions) التي قد تحدث عندما يحاول عدة مسارات (threads) تحديث نفس المتغير بشكل متزامن. في هذه الحالة، يتسبب الكائن B، الذي يمتلك مسارًا منفصلًا، في زيادة قيمة X، ولكن قبل أن يتم ذلك، يقوم الكائن A بطباعة قيمة X الحالية.

باستخدام الكلمة المفتاحية synchronized، يتم تجنب هذه التداخلات بضمان أنه يمكن لـ A و B الوصول إلى المتغير X بشكل آمن ومتزامن. وبالتالي، يضمن البرنامج الآن حصولنا على سلسلة تصاعدية من القيم، وليس فقط قيمة واحدة.

زر الذهاب إلى الأعلى