0. رمز السؤال الرائد
يوضح الرمز التالي عدادًا حيث يتراكم مؤشر ترابط I في نفس الوقت ، حيث يقوم كل منها 1000،000 مرة. النتيجة التي نتوقعها هي بالتأكيد أنا = 2000000. ومع ذلك ، بعد تنفيذها عدة مرات ، سنجد أن قيمة I سأكون دائمًا أقل من 2000000. هذا لأنه عندما يكتب خيطان في نفس الوقت ، فإن نتيجة أحد المواضيع ستحقق الآخر.
الطبقة العامة AccountingSync تنفذ Runnable {static int i = 0 ؛ زيادة الفراغ العام () {i ++ ؛ } Override public void run () {for (int j = 0 ؛ j <1000000 ؛ j ++) {student () ؛ }} public static void main (string [] args) remrows interruptedException {AccountingSync AccountingSync = new AccountingSync () ؛ الموضوع T1 = مؤشر ترابط جديد (AccountingSync) ؛ الموضوع T2 = مؤشر ترابط جديد (AccountingSync) ؛ t1.start () ؛ t2.start () ؛ t1.join () ؛ t2.join () ؛ system.out.println (i) ؛ }} لحل هذه المشكلة بشكل أساسي ، يجب علينا التأكد من أنه يجب مزامنة مؤشرات ترابط متعددة بالكامل عند تشغيل i. وهذا يعني ، عندما لا يستطيع الخيط A كتابة I ، الموضوع B لا يمكن الكتابة فحسب ، بل لا يمكنه أيضًا قراءته.
1. دور الكلمات الرئيسية المتزامنة
وظيفة الكلمة الرئيسية المتزامنة هي في الواقع إدراك التزامن بين المواضيع. تتمثل مهمتها في قفل الكود المتزامن ، بحيث يمكن لخيط واحد فقط إدخال كتلة المزامنة في وقت واحد ، وبالتالي ضمان الأمان بين المواضيع. كما هو الحال في الكود أعلاه ، لا يمكن تنفيذ عملية i ++ إلا بواسطة مؤشر ترابط آخر في نفس الوقت.
2. استخدام الكلمات الرئيسية المتزامنة
حدد قفل الكائن: قفل الكائن المحدد ، أدخل كتلة رمز التزامن للحصول على قفل الكائن المحدد
يتصرف مباشرة على طريقة المثيل: إنه يعادل قفل المثيل الحالي. عند إدخال كتلة التعليمات البرمجية المتزامنة ، يجب الحصول على قفل المثيل الحالي (يتطلب ذلك أنه عند إنشاء مؤشر ترابط ، يجب عليك استخدام نفس المثيل القابل للتشغيل)
يتصرف مباشرة على الطرق الثابتة: إنه يعادل قفل الفئة الحالية. قبل إدخال كتلة التعليمات البرمجية المتزامنة ، يجب عليك الحصول على قفل الفئة الحالية.
2.1 حدد الكائن للقفل
ينطبق الكود التالي متزامن مع كائن معين. هناك ملاحظة هنا على أن الكائن المعطى يجب أن يكون ثابتًا ، وإلا فلن نشارك الكائن مع بعضنا البعض في كل مرة نرغب فيها في سلسلة رسائل ، وبالتالي فإن معنى القفل لم يعد موجودًا.
الطبقة العامة AccountingSync تنفذ Runnable {Final Static Object = New Object () ؛ ثابت int i = 0 ؛ زيادة الفراغ العام () {i ++ ؛ } Override public void run () {for (int j = 0 ؛ j <1000000 ؛ j ++) {synchronized (object) {student () ؛ }}} public static void main (string [] args) remrows interruptedException {thread t1 = new thread (new AccountingSync ()) ؛ الموضوع T2 = مؤشر ترابط جديد (AccountingSync ()) ؛ t1.start () ؛ t2.start () ؛ t1.join () ؛ t2.join () ؛ system.out.println (i) ؛ }} 2.2 يعمل مباشرة على طريقة المثيل
تعمل الكلمة الرئيسية المتزامنة على طريقة المثيل ، أي قبل إدخال طريقة الزيادة () ، يجب أن يحصل مؤشر الترابط على قفل المثيل الحالي. هذا يتطلب منا استخدام مثيل الكائن القابل للتشغيل نفسه عند إنشاء مثيل مؤشر الترابط. خلاف ذلك ، فإن أقفال الخيط ليست على نفس الحالة ، لذلك لا توجد طريقة للحديث عن مشكلة القفل/التزامن.
الطبقة العامة AccountingSync تنفذ Runnable {static int i = 0 ؛ زيادة void المزامنة العامة () {i ++ ؛ } Override public void run () {for (int j = 0 ؛ j <1000000 ؛ j ++) {student () ؛ }} public static void main (string [] args) remrows interruptedException {AccountingSync AccountingSync = new AccountingSync () ؛ الموضوع T1 = مؤشر ترابط جديد (AccountingSync) ؛ الموضوع T2 = مؤشر ترابط جديد (AccountingSync) ؛ t1.start () ؛ t2.start () ؛ t1.join () ؛ t2.join () ؛ system.out.println (i) ؛ }} يرجى الانتباه إلى الأسطر الثلاثة الأولى من الطريقة الرئيسية لتوضيح الاستخدام الصحيح للكلمات الرئيسية في طريقة المثيل.
2.3 يتصرف مباشرة على الطرق الثابتة
لتطبيق الكلمة الرئيسية المتزامنة على الطريقة الثابتة ، ليست هناك حاجة لاستخدام الخيطين للإشارة إلى نفس الطريقة القابلة للتشغيل كما في المثال أعلاه. نظرًا لأن كتلة الطريقة تحتاج إلى طلب قفل الفئة الحالية ، وليس المثيل الحالي ، لا يزال من الممكن مزامنة مؤشرات الترابط بشكل صحيح.
الطبقة العامة AccountingSync تنفذ Runnable {static int i = 0 ؛ زيادة الفراغ المتزامن العام الثابت () {i ++ ؛ } Override public void run () {for (int j = 0 ؛ j <1000000 ؛ j ++) {student () ؛ }} public static void main (string [] args) remrows interruptedException {thread t1 = new thread (new AccountingSync ()) ؛ الموضوع T2 = مؤشر ترابط جديد (AccountingSync ()) ؛ t1.start () ؛ t2.start () ؛ t1.join () ؛ t2.join () ؛ system.out.println (i) ؛ }}3. قفل غير صحيح
من المثال أعلاه ، نعلم أنه إذا كنا بحاجة إلى تطبيق مضاد ، من أجل ضمان صحة البيانات ، سنحتاج بطبيعة الحال إلى قفل العداد ، لذلك يمكننا كتابة الرمز التالي:
الطبقة العامة badlockoninteger تنفذ runnable {static integer i = 0 ؛ Override public void run () {for (int j = 0 ؛ j <1000000 ؛ j ++) {synchronized (i) {i ++ ؛ }}} static void main (string [] args) remrows interruptedException {badlockoninteger badlockoninteger = new badlockoninteger () ؛ الموضوع T1 = مؤشر ترابط جديد (badlockoninteger) ؛ الموضوع T2 = مؤشر ترابط جديد (badlockoninteger) ؛ t1.start () ؛ t2.start () ؛ t1.join () ؛ t2.join () ؛ system.out.println (i) ؛ }}عندما نقوم بتشغيل الكود أعلاه ، سنجد أن الإخراج أنا صغير جدًا. هذا يعني أن الموضوع غير آمن.
لشرح هذه المشكلة ، نحتاج إلى البدء في عدد صحيح: في جافا ، يعد Integer كائنًا ثابتًا. مثل السلسلة ، بمجرد إنشاء كائن ، لا يمكن تعديله. إذا كان لديك عدد صحيح = 1 ، فستكون دائمًا 1. ماذا لو كنت تريد هذا الكائن = 2؟ يمكنك إعادة إنشاء عدد صحيح فقط. بعد كل i ++ ، فإنه يعادل استدعاء طريقة قيمة عدد صحيح. دعنا نلقي نظرة على رمز المصدر لأسلوب قيمة integer:
public static integer valueof (int i) {if (i> = integercache.low && i <= integercache.high) إرجاع integercache.cache [i + (-integerCache.low)] ؛ إرجاع عدد صحيح جديد (i) ؛} integer.valueof () هي في الواقع طريقة مصنع ، والتي تميل إلى إعادة كائن عدد صحيح جديد ونسخ القيمة إلى i ؛
لذلك ، نحن نعرف سبب المشكلة. نظرًا لأن بين مؤشرات الترابط المتعددة ، نظرًا لأن i ++ يأتي بعد i ، أشير إلى كائن جديد ، فقد يقوم مؤشر الترابط بتحميل مثيلات كائن مختلفة في كل مرة يتم قفلها. الحل بسيط للغاية. يمكنك حلها باستخدام إحدى طرق المزامنة الثلاثة أعلاه.