Synchronized هي آلية مزامنة مدمجة في لغة Java توفر وصولاً حصرياً إلى الأقسام الحرجة من الكود. وفقاً لـ Oracle، 2024، يضمن المعدل synchronized أن خيطاً واحداً فقط يمكنه تنفيذ الطريقة أو الكتلة المحددة في لحظة زمنية محددة. تعتمد هذه الآلية على الشاشات (monitors) — مفهوم أساسي في أنظمة التشغيل يضمن التشغيل الصحيح للتطبيقات متعددة الخيوط بجميع مستويات التعقيد.
الملخص
Synchronized هي كلمة مفتاحية في Java تضمن أن خيطاً واحداً فقط ينفذ مقطعاً محمياً من الكود في أي وقت، مما يمنع تلف البيانات أثناء الوصول المتزامن. ظهرت في الإصدار الأول من Java ولا تزال أبسط طريقة لضمان أمان الخيوط للمطورين من أي مستوى خبرة.
يحل المعدل synchronized مهمتين: الاستبعاد المتبادل و رؤية التغييرات. عندما يخرج خيط من كتلة synchronized، تكون جميع التغييرات مرئية للخيوط الأخرى التي تدخل كتلة متزامنة على نفس الكائن.
يمكن تطبيق synchronized على طريقة كاملة أو على كتلة كود عشوائية مع تحديد كائن المراقب. في كلتا الحالتين، تقوم JVM بإدراج تعليمات monitorenter و monitorexit على مستوى البايت كود.
في التطبيقات متعددة الخيوط دون مزامنة، تحدث حالة تسابق (race condition) عندما يقوم خيطان بتعديل نفس البيانات في وقت واحد، مما يؤدي إلى نتائج غير متوقعة. أصبح synchronized الأداة الأولى والرئيسية في Java لمكافحة هذه المشكلة، حيث يوفر بناء جملة تصريحي بسيط يمكن لأي مطور استخدامه.
تعتمد آلية synchronized على مفهوم الشاشة (monitor) — وهو بدائي مزامنة عالي المستوى مدمج في كل كائن Java. ترتبط الشاشة بكائن عند أول استخدام لكتلة synchronized عليه.
كل كائن في Java له شاشة مرتبطة به. عندما يدخل خيط إلى كتلة synchronized، فإنه يستحوذ على شاشة الكائن. إذا كانت الشاشة مشغولة بالفعل بخيط آخر، يتم حظر الخيط حتى يتم تحريرها. في البايت كود، يتوافق هذا مع زوج التعليمات monitorenter و monitorexit.
تعمل JVM على تحسين synchronized من خلال عدة مستويات: biased locking (القفل المنحاز) للوصول أحادي الخيط، lightweight locking (قفل خفيف) للتنافس المنخفض، و heavyweight locking (قفل ثقيل) للتنافس الشديد بمشاركة نظام التشغيل. تعمل هذه المستويات على تحسين الأداء دون تغيير الكود.
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
يؤسس synchronized علاقة happens-before: جميع الإجراءات في خيط قبل الخروج من كتلة synchronized تكون مرئية لخيط آخر بعد الدخول إلى كتلة متزامنة على نفس الكائن. يضمن هذا ليس فقط الاستبعاد المتبادل ولكن أيضاً اتساق البيانات لجميع الخيوط.
تقدم Java طريقتين لتطبيق synchronized: على مستوى الطريقة وعلى مستوى الكتلة. الاختيار بينهما يؤثر على الأداء ودقة المزامنة.
تحديد طريقة بالمعدل synchronized يؤدي تلقائياً إلى مزامنتها على المثيل الحالي (للطرق العادية) أو على كائن Class (للطرق الثابتة). هذه هي أبسط طريقة لضمان الاستبعاد المتبادل، لكنها غالباً ما تكون مفرطة إذا كان القسم الحرج يشكل جزءاً صغيراً فقط من الطريقة، وبقية الكود لا يتطلب مزامنة.
تعطي كتلة synchronized تحكماً دقيقاً: تحدد كائن المراقب وتزامن فقط الجزء الضروري من الكود، تاركاً بقية الطريقة خارج القفل. يقلل هذا من وقت الاحتفاظ بالشاشة ويحسن الأداء العام للتطبيق في بيئة متعددة الخيوط، حيث يمكن للخيوط الأخرى تنفيذ كود غير ذي صلة بالتوازي دون انتظار تحرير الشاشة.
class DataProcessor {
private final Object lock = new Object();
public void process() {
// كود خارج القسم الحرج - بدون مزامنة
prepareData()
synchronized (lock) {
// فقط هذه الكتلة محمية
updateSharedState()
}
// استمرار بدون قفل
cleanup()
}
}
| المعيار | طريقة synchronized | كتلة synchronized |
|---|---|---|
| المراقب | this (مثيل) أو Class | أي كائن |
| دقة المزامنة | الطريقة بأكملها | فقط الكود المطلوب |
| قابلية القراءة | عالية | متوسطة |
| الأداء | أقل للطرق الكبيرة | أعلى للأقسام الحرجة الصغيرة |
في تطوير Android، يُستخدم synchronized على نطاق واسع لحماية SharedPreferences والوصول إلى قواعد البيانات ومكونات واجهة المستخدم. ومع ذلك، فإن استخدامه في الخيط الرئيسي غير موصى به بشدة بسبب خطر تجميد الواجهة.
توفر SharedPreferences في Android أماناً أساسياً بين الخيوط، ولكن عند التحرير من خيوط متعددة عبر Editor، قد تكون المزامنة الخارجية ضرورية. تضمن كتلة synchronized مع كائن قفل منفصل اتساق التغييرات.
class PreferencesManager(private val prefs: SharedPreferences) {
private val lock = Any()
fun writeToken(token: String) {
synchronized (lock) {
prefs.edit()
.putString("auth_token", token)
.apply()
}
}
}
القيود الرئيسية لـ synchronized على Android هي حظر الخيط. على عكس coroutines مع Mutex، يقوم synchronized بحظر خيط النظام بالكامل. في الخيط الرئيسي، يتسبب هذا في ANR. في تطوير Android الحديث، يُوصى باستبدال synchronized بـ coroutines (suspend Mutex) أو الأنواع الذرية (AtomicInteger).
تقدم Java و Kotlin الحديثة عدة بدائل لـ synchronized، كل منها يحل نفس المشاكل بقيود أقل أو أداء أفضل.
توفر واجهة Lock مع تطبيقات ReentrantLock و ReadWriteLock مهلات زمنية وانتظاراً قابلاً للمقاطعة وعدة قوائم Condition. إنها أكثر مرونة من synchronized ولكنها تتطلب تحريراً صريحاً في finally، مما يزيد من خطر الخطأ عند نسيان unlock.
AtomicInteger و AtomicLong و AtomicReference وغيرها تستخدم خوارزميات Lock-Free القائمة على CAS (Compare-And-Swap). إنها أسرع بكثير من synchronized في سيناريوهات التنافس المعتدل لأنها لا تحظر الخيوط بل تقوم بإعادة محاولات متفائلة دون الحاجة إلى تبديل سياق نواة نظام التشغيل.
يوفر ThreadLocal نهجاً بديلاً: كل متغير ThreadLocal معزول داخل خيط واحد ولا يتطلب مزامنة للقراءة والكتابة. هذا يلغي تماماً الحاجة إلى synchronized للبيانات التي لا ينبغي مشاركتها بين الخيوط. يُستخدم ThreadLocal بنشاط في الأطر (Spring, Hibernate) لتخزين سياق المعاملات والجلسات.
في مشاريع Kotlin لنظام Android، البديل لـ synchronized هو Mutex من kotlinx.coroutines. لا يقوم بحظر خيط نظام التشغيل بل يعلق coroutine حتى يتم تحرير القفل — مما يسمح باستخدام فعال لخيوط pool وتجنب ANR أثناء الانتظار الطويل لتحرير المورد.
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
val mutex = Mutex()
var counter = 0
suspend fun safeIncrement() {
mutex.withLock {
counter++
}
}
تغير أداء synchronized بشكل كبير في إصدارات Java الأخيرة. كان يُعتبر في السابق آلية “ثقيلة”، لكن JVMs الحديثة أزالت معظم الحمل الزائد من خلال تحسينات متقدمة لمترجم JIT. دعونا نلقي نظرة مفصلة على كيفية تسريع الآلة الافتراضية للكود المتزامن في وقت التشغيل.
يطبق مترجم JIT في JVM عدة تحسينات: biased locking يلغي المزامنة إذا كان القفل يُستحوذ عليه دائماً بواسطة نفس الخيط؛ lock coarsening يدمج كتل synchronized المتجاورة في كتلة واحدة؛ lock elimination يزيل المزامنة إذا كان الكائن يمكن الوصول إليه بواسطة خيط واحد فقط. هذه التحسينات تجعل synchronized مجانياً تقريباً عند التنافس المنخفض.
تحدد JVM مستوى التنافس لكل كائن: عندما لا يكون هناك تنافس، يتم تفعيل biased locking؛ عندما يظهر خيط ثانٍ، ينتقل القفل إلى الوضع الخفيف مع انتظار دوراني (spin)؛ وفقط أثناء الانتظار الطويل يتصاعد إلى الوضع الثقيل مع mutex لنظام التشغيل. يحدث هذا التصعيد تلقائياً، ولا يحتاج المطور إلى اختيار استراتيجية يدوياً.
في المقاييس الحديثة (Java 17+)، يُظهر synchronized أداءً مماثلاً لـ ReentrantLock عند التنافس المنخفض والمعتدل. عند التنافس العالي، قد يكون Lock أفضل بفضل قائمة انتظار أكثر كفاءة مع دعم المهلات الزمنية والمقاطعة. للأنظمة عالية التحميل حيث التنافس ثابت، يوفر ReentrantLock مع الوضع العادل (fair) سلوكاً أكثر قابلية للتنبؤ.
تبقى الفئات الذرية (AtomicInteger, AtomicReference) الأسرع للعدادات والأعلام البسيطة بفضل تنفيذ Lock-Free القائم على CAS. لا تحظر الخيوط على الإطلاق — عند حدوث تعارض، يتم simply إعادة محاولة العملية في حلقة. يعطي هذا زيادة في الأداء تتراوح من 3 إلى 5 مرات مقارنة بـ synchronized في عمليات زيادة العداد مع 4-8 خيوط.
الأسئلة الشائعة
Synchronized يوفر كلاً من الاستبعاد المتبادل والرؤية. Volatile يضمن فقط رؤية التغييرات — الكتابة إلى متغير volatile مرئية لجميع الخيوط لكنها لا تمنع التعديل المتزامن، أي أنها لا تحمي من حالة التسابق.
نعم، deadlock ممكن مع المزامنة المتداخلة باستخدام ترتيب مختلف للشاشات. على سبيل المثال، خيط يستدعي synchronized(a) { synchronized(b) }، بينما آخر يستدعي synchronized(b) { synchronized(a) }. تجنب كتل synchronized المتداخلة أو حدد ترتيباً ثابتاً للشاشات.
الشاشة هي آلية مزامنة مرتبطة بكل كائن Java. تضمن أن خيطاً واحداً فقط ينفذ كود synchronized على ذلك الكائن. تتضمن الشاشة قفلاً وقائمة انتظار ومجموعة من الخيوط المنتظرة للإشعار عبر wait/notify.
في إصدارات Java الحديثة (17+)، synchronized لا يتخلف عن Lock في الأداء بفضل تحسينات JIT (biased locking, lock coarsening). يُفضل Lock ليس للسرعة بل للإمكانيات الإضافية: المهلات الزمنية والانتظار القابل للمقاطعة وعدة قوائم Condition.
الطريقة الثابتة synchronized تستخدم شاشة كائن Class للفئة المحددة، وليس المثيل. هذا يعني أن المزامنة تنطبق على جميع مثيلات الفئة. تستخدم الطرق غير الثابتة والثابتة synchronized شاشات مختلفة ولا تحجب بعضها البعض.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا