«ماذا يعني synchronized؟» — سؤال يقلق كل مبتدئ ثانٍ
\n\nإذا كنت قد كتبت تطبيقًا متعدد الخيوط في Java، فبالتأكيد واجهت موقفًا: خيطان يغيران متغيرًا واحدًا في نفس الوقت، والنتيجة — أخطاء تظهر مرة كل مئة تشغيل. مألوف؟ لهذا السبب تحديدًا توجد الكلمة المفتاحية synchronized. ولكن ماذا تفعل حقًا؟ كيف تعمل «تحت الغطاء»؟ والأهم — متى نستخدمها، ومتى نستغني عنها؟
في هذه المقالة، لن نعيد سرد الوثائق فقط. سنحلل سؤالًا شائعًا من Stack Overflow (ذلك الذي جمع آلاف المشاهدات)، ونكيّفه لصيغة تعليمية، ونعرض مقارنة بين الأساليب: المزامنة على مستوى الطريقة، وعلى مستوى الكتلة، والبدائل من java.util.concurrent. هيا بنا!
المشكلة: سباق الخيوط (Race Condition)
\n\nتخيل: لديك عداد يزيده خيطان. الكود بسيط، لكن النتيجة غير متوقعة.
\n\npublic class Counter {\n private int count = 0;\n\n public void increment() {\n count++;\n }\n\n public int getCount() {\n return count;\n }\n}\n\nنشغّل خيطين، كل منهما يقوم بـ 1000 زيادة. من المفترض أن تكون النتيجة 2000. لكن في الواقع — 1987، 1993، أو حتى 1765. لماذا؟ لأن العملية count++ ليست ذرية. تتكون من ثلاث خطوات: قراءة → زيادة → كتابة. تتداخل الخيوط، ويضيع أحد التغييرات.
هذه هي حالة السباق (race condition). و synchronized هي إحدى الأدوات الرئيسية لمكافحتها.
ماذا يفعل synchronized؟ نظرة تحت الغطاء
\n\nالكلمة المفتاحية synchronized في Java تضمن الاستبعاد المتبادل (mutex) و رؤية التغييرات بين الخيوط. عندما يدخل خيط إلى كتلة synchronized، فإنه يلتقط شاشة (monitor) الكائن (أو الفئة). الخيوط الأخرى التي تحاول الدخول إلى أي كتلة synchronized على نفس الشاشة تُحظر حتى يتم تحريرها.
هام: synchronized يعمل فقط مع شاشة واحدة. إذا كان لديك كائنان مختلفان — لن تعمل المزامنة.
مستويان للمزامنة
\n- \n
- طريقة متزامنة — تحظر الطريقة بأكملها، وتستخدم شاشة الكائن (
this). \n - كتلة متزامنة — تحظر جزءًا فقط من الكود، ويمكن تحديد أي كائن كشاشة. \n
لنقارن بينهما عمليًا.
\n\nمقارنة الأساليب: طريقة مقابل كتلة
\n\n1. الطريقة المتزامنة
\n\nأبسط طريقة — إضافة synchronized إلى توقيع الطريقة.
public class SafeCounter {\n private int count = 0;\n\n public synchronized void increment() {\n count++;\n }\n\n public synchronized int getCount() {\n return count;\n }\n}\n\nالإيجابيات:
\n- \n
- أقل كود، سهل القراءة. \n
- يضمن أن الطريقة بأكملها عملية ذرية. \n
السلبيات:
\n- \n
- قفل «ثقيل» — حتى لو كان داخل الطريقة سطر واحد فقط، يُحظر الخيط طوال مدة التنفيذ. \n
- يقلل الأداء إذا كانت الطريقة طويلة أو تُستدعى كثيرًا. \n
- غير مرن — لا يمكن مزامنة جزء فقط من المنطق. \n
2. الكتلة المتزامنة
\n\nتحكم أدق. أنت من يقرر أي كائن يُستخدم كشاشة.
\n\npublic class SafeCounterWithBlock {\n private int count = 0;\n private final Object lock = new Object();\n\n public void increment() {\n synchronized (lock) {\n count++;\n }\n }\n\n public int getCount() {\n synchronized (lock) {\n return count;\n }\n }\n}