تعد البيانات المشتركة واحدة من أهم الميزات للبرامج المتزامنة. يعد هذا جانبًا مهمًا جدًا سواء كان كائنًا يرث فئة Thread أو كائنًا ينفذ واجهة قابلة للتشغيل.
إذا قمت بإنشاء كائن من فئة يقوم بتنفيذ واجهة Runnable واستخدمت هذا الكائن لبدء سلسلة من سلاسل العمليات، فإن كل هذه الخيوط تشترك في نفس الخصائص. بمعنى آخر، إذا قام مؤشر ترابط واحد بتعديل خاصية ما، فستتأثر كافة مؤشرات الترابط المتبقية بالتغيير.
في بعض الأحيان، نفضل استخدامه بمفرده داخل سلسلة رسائل بدلاً من مشاركته مع سلاسل رسائل أخرى تبدأ بنفس الكائن. توفر واجهة Java المتزامنة آلية واضحة جدًا لتلبية هذا المطلب، تسمى متغيرات مؤشر الترابط المحلي. أداء هذه الآلية مثير للإعجاب أيضًا.
تعرف عليه
اتبع الخطوات الموضحة أدناه لإكمال نموذج البرنامج.
1. أولاً، قم بتنفيذ برنامج يحتوي على المشكلات المذكورة أعلاه. قم بإنشاء فئة باسم UnsafeTask وقم بتنفيذ واجهة Runnable. قم بتعريف خاصية خاصة من النوع java.util.Date في الفصل الدراسي. الرمز هو كما يلي:
انسخ رمز الكود كما يلي:
الطبقة العامة UnsafeTask تنفذ Runnable {
تاريخ البدء الخاص؛
2. قم بتنفيذ طريقة التشغيل () الخاصة بـ UnsafeTask، والتي تقوم بإنشاء مثيل لسمة startDate وإخراج قيمتها إلى وحدة التحكم. قم بالنوم لفترة عشوائية من الوقت ثم قم بطباعة قيمة السمة startDate إلى وحدة التحكم مرة أخرى. الرمز هو كما يلي:
انسخ رمز الكود كما يلي:
@تجاوز
تشغيل الفراغ العام () {
startDate = new Date();
System.out.printf ("بدء الموضوع: %s : %s/n"،
Thread.currentThread().getId(), startDate);
يحاول {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} قبض على (InterruptedException e) {
printStackTrace();
}
System.out.printf ("انتهى الموضوع: %s : %s/n"،
Thread.currentThread().getId(), startDate);
}
3. تنفيذ الفصل الرئيسي للبرنامج الإشكالي. أنشئ فصلًا باستخدام الطريقة الرئيسية () UnsafeMain. في الطريقة main()، قم بإنشاء كائن UnsafeTask واستخدم هذا الكائن لإنشاء 10 كائنات Thread لبدء 10 سلاسل رسائل. في منتصف كل خيط، نم لمدة ثانيتين. الرمز هو كما يلي:
انسخ رمز الكود كما يلي:
الطبقة العامة غير الآمنة {
public static void main(String[] args) {
مهمة UnsafeTask = New UnsafeTask();
لـ (int i = 0; i < 10; i++) {
موضوع الموضوع = موضوع جديد (مهمة)؛
Thread.start();
يحاول {
TimeUnit.SECONDS.sleep(2);
} قبض على (InterruptedException e) {
printStackTrace();
}
}
}
}
4. من المنطق أعلاه، كل موضوع له وقت بدء تشغيل مختلف. ومع ذلك، وفقًا لسجل الإخراج أدناه، هناك العديد من قيم الوقت المتطابقة. على النحو التالي:
انسخ رمز الكود كما يلي:
موضوع البداية: 9: الأحد 29 سبتمبر الساعة 23:31:08 بتوقيت وسط أمريكا 2013
موضوع البداية: 10: الأحد 29 سبتمبر 23:31:10 CST 2013
موضوع البداية: 11: الأحد 29 سبتمبر 23:31:12 CST 2013
موضوع البداية: 12: الأحد 29 سبتمبر 23:31:14 CST 2013
انتهى الموضوع: 9: الأحد 29 سبتمبر الساعة 23:31:14 بتوقيت وسط أمريكا 2013
موضوع البداية: 13: الأحد 29 سبتمبر 23:31:16 CST 2013
انتهى الموضوع: 10: الأحد 29 سبتمبر الساعة 23:31:16 بتوقيت وسط أمريكا 2013
موضوع البداية: 14: الأحد 29 سبتمبر 23:31:18 CST 2013
انتهى الموضوع: 11: الأحد 29 سبتمبر الساعة 23:31:18 بتوقيت وسط أمريكا 2013
موضوع البداية: 15: الأحد 29 سبتمبر الساعة 23:31:20 بتوقيت وسط أمريكا 2013
انتهى الموضوع: 12: الأحد 29 سبتمبر الساعة 23:31:20 بتوقيت وسط أمريكا 2013
موضوع البداية: 16: الأحد 29 سبتمبر 23:31:22 CST 2013
موضوع البداية: 17: الأحد 29 سبتمبر الساعة 23:31:24 بتوقيت وسط أمريكا 2013
انتهى الموضوع: 17: الأحد 29 سبتمبر الساعة 23:31:24 بتوقيت وسط أمريكا 2013
انتهى الموضوع: 15: الأحد 29 سبتمبر الساعة 23:31:24 بتوقيت وسط أمريكا 2013
انتهى الموضوع: 13: الأحد 29 سبتمبر الساعة 23:31:24 بتوقيت وسط أمريكا 2013
موضوع البداية: 18: الأحد 29 سبتمبر 23:31:26 CST 2013
انتهى الموضوع: 14: الأحد 29 سبتمبر الساعة 23:31:26 بتوقيت وسط أمريكا 2013
انتهى الموضوع: 18: الأحد 29 سبتمبر الساعة 23:31:26 بتوقيت وسط أمريكا 2013
انتهى الموضوع: 16: الأحد 29 سبتمبر الساعة 23:31:26 بتوقيت وسط أمريكا 2013
5. كما هو موضح أعلاه، سنستخدم آلية متغيرات الخيط المحلي لحل هذه المشكلة.
6. قم بإنشاء فئة باسم SafeTask وقم بتنفيذ الواجهة القابلة للتشغيل. الرمز هو كما يلي:
انسخ رمز الكود كما يلي:
الطبقة العامة SafeTask تنفذ Runnable {
7. قم بتعريف كائن من النوع ThreadLocal<Date> عندما يتم إنشاء مثيل للكائن، يتم تجاوز الأسلوب الأولي () ويتم إرجاع قيمة التاريخ الفعلية في هذه الطريقة. الرمز هو كما يلي:
انسخ رمز الكود كما يلي:
خاص ثابت ThreadLocal<Date> startDate = جديد
الموضوع المحلي<التاريخ>() {
@تجاوز
التاريخ المحمي القيمة الأولية () {
إرجاع تاريخ جديد ()؛
}
};
8. قم بتنفيذ طريقة التشغيل () لفئة SafeTask. هذه الطريقة هي نفس طريقة التشغيل () الخاصة بـ UnsafeTask، باستثناء أن طريقة السمة startDate تم تعديلها قليلاً. الرمز هو كما يلي:
انسخ رمز الكود كما يلي:
@تجاوز
تشغيل الفراغ العام () {
System.out.printf ("بدء الموضوع: %s : %s/n"،
Thread.currentThread().getId(), startDate.get());
يحاول {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} قبض على (InterruptedException e) {
printStackTrace();
}
System.out.printf ("انتهى الموضوع: %s : %s/n"،
Thread.currentThread().getId(), startDate.get());
}
9. الفئة الرئيسية لهذا المثال الآمن هي في الأساس نفس الفئة الرئيسية للبرنامج غير الآمن، باستثناء أن UnsafeTask يحتاج إلى تعديل إلى SafeTask. الكود المحدد هو كما يلي:
انسخ رمز الكود كما يلي:
الطبقة العامة SafeMain {
public static void main(String[] args) {
مهمة SafeTask = SafeTask الجديدة ()؛
لـ (int i = 0; i < 10; i++) {
موضوع الموضوع = موضوع جديد (مهمة)؛
Thread.start();
يحاول {
TimeUnit.SECONDS.sleep(2);
} قبض على (InterruptedException e) {
printStackTrace();
}
}
}
}
10. قم بتشغيل البرنامج وتحليل الاختلافات بين المدخلين.
من أجل توحيد تسمية الفئات، تختلف تسمية الفئة الرئيسية في هذه المقالة قليلاً عن النص الأصلي. بالإضافة إلى ذلك، البرنامج الأصلي ووصف النص غير متناسقين. يجب أن يكون خطأ كتابي.
أعرف لماذا
فيما يلي نتيجة تنفيذ المثال الأمني. من النتائج، يمكن أن نرى بسهولة أن كل موضوع لديه قيمة سمة startDate تنتمي إلى الموضوع المعني. مدخلات البرنامج هي كما يلي:
انسخ رمز الكود كما يلي:
موضوع البداية: 9: الأحد 29 سبتمبر 23:52:17 CST 2013
موضوع البداية: 10: الأحد 29 سبتمبر 23:52:19 CST 2013
موضوع البداية: 11: الأحد 29 سبتمبر 23:52:21 CST 2013
انتهى الموضوع: 10: الأحد 29 سبتمبر الساعة 23:52:19 بتوقيت وسط أمريكا 2013
موضوع البداية: 12: الأحد 29 سبتمبر 23:52:23 CST 2013
انتهى الموضوع: 11: الأحد 29 سبتمبر الساعة 23:52:21 بتوقيت وسط أمريكا 2013
موضوع البداية: 13: الأحد 29 سبتمبر الساعة 23:52:25 بتوقيت وسط أمريكا 2013
انتهى الموضوع: 9: الأحد 29 سبتمبر الساعة 23:52:17 بتوقيت وسط أمريكا 2013
موضوع البداية: 14: الأحد 29 سبتمبر 23:52:27 CST 2013
موضوع البداية: 15: الأحد 29 سبتمبر 23:52:29 CST 2013
انتهى الموضوع: 13: الأحد 29 سبتمبر الساعة 23:52:25 بتوقيت وسط أمريكا 2013
موضوع البداية: 16: الأحد 29 سبتمبر 23:52:31 CST 2013
انتهى الموضوع: 14: الأحد 29 سبتمبر الساعة 23:52:27 بتوقيت وسط أمريكا 2013
موضوع البداية: 17: الأحد 29 سبتمبر الساعة 23:52:33 بتوقيت وسط أمريكا 2013
انتهى الموضوع: 12: الأحد 29 سبتمبر الساعة 23:52:23 بتوقيت وسط أمريكا 2013
انتهى الموضوع: 16: الأحد 29 سبتمبر الساعة 23:52:31 بتوقيت وسط أمريكا 2013
انتهى الموضوع: 15: الأحد 29 سبتمبر الساعة 23:52:29 بتوقيت وسط أمريكا 2013
موضوع البداية: 18: الأحد 29 سبتمبر الساعة 23:52:35 بتوقيت وسط أمريكا 2013
انتهى الموضوع: 17: الأحد 29 سبتمبر الساعة 23:52:33 بتوقيت وسط أمريكا 2013
انتهى الموضوع: 18: الأحد 29 سبتمبر الساعة 23:52:35 بتوقيت وسط أمريكا 2013
تقوم متغيرات مؤشر الترابط المحلية بتخزين نسخة من الخاصية لكل مؤشر ترابط. يمكنك استخدام طريقة get() الخاصة بـ ThreadLocal للحصول على قيمة المتغير، واستخدام طريقة set() لتعيين قيمة المتغير. إذا تم الوصول إلى متغير مؤشر الترابط المحلي لأول مرة ولم يتم تعيين قيمة للمتغير بعد، فسيتم استدعاء الأسلوب الأوليValue() لتهيئة قيمة لكل مؤشر ترابط.
لا تنتهي أبدا
توفر فئة ThreadLocal أيضًا طريقة الإزالة () لحذف قيمة المتغير المحلي المخزنة في مؤشر الترابط الذي يستدعي هذه الطريقة.
بالإضافة إلى ذلك، توفر واجهة برمجة تطبيقات Java المتزامنة أيضًا فئة InheritableThreadLocal، والتي تسمح للخيط الفرعي بتلقي القيم الأولية لجميع المتغيرات المحلية للخيط القابل للتوريث للحصول على القيمة التي يملكها الخيط الأصلي. إذا كان مؤشر الترابط A يحتوي على متغير محلي لمؤشر الترابط، فعندما يقوم مؤشر الترابط A بإنشاء مؤشر ترابط B، سيكون لمؤشر الترابط B نفس المتغير المحلي لمؤشر الترابط مثل مؤشر الترابط A. يمكنك أيضًا تجاوز ChildValue() لتهيئة المتغيرات المحلية لمؤشر الترابط التابع. ستقبل هذه الطريقة قيمة متغير مؤشر الترابط المحلي الذي تم تمريره كمعلمة من مؤشر الترابط الأصلي.
استخدام العقيدة
تمت ترجمة هذه المقالة من "Java 7 Concurrency Cookbook" (سرقها D Gua Ge باسم "مجموعة أمثلة Java7 Concurrency") ويتم استخدامها فقط كمواد تعليمية. ولا يجوز استخدامه لأية أغراض تجارية دون تصريح.
نجاح صغير
يوجد أدناه نسخة كاملة من كافة التعليمات البرمجية المضمنة في الأمثلة في هذا القسم.
الكود الكامل لفئة UnsafeTask:
انسخ رمز الكود كما يلي:
الحزمة com.diguage.books.concurrencycookbook.chapter1.recipe9؛
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* أمثلة حيث لا يمكن ضمان سلامة الخيط
* التاريخ: 2013-09-23
*الزمن: 23:58
*/
الطبقة العامة UnsafeTask تنفذ Runnable {
تاريخ البدء الخاص؛
@تجاوز
تشغيل الفراغ العام () {
startDate = new Date();
System.out.printf ("بدء الموضوع: %s : %s/n"،
Thread.currentThread().getId(), startDate);
يحاول {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} قبض على (InterruptedException e) {
printStackTrace();
}
System.out.printf ("انتهى الموضوع: %s : %s/n"،
Thread.currentThread().getId(), startDate);
}
}
الكود الكامل لفئة UnsafeMain:
انسخ رمز الكود كما يلي:
الحزمة com.diguage.books.concurrencycookbook.chapter1.recipe9؛
import java.util.concurrent.TimeUnit;
/**
* مثال على موضوع غير آمن
* التاريخ: 2013-09-24
*التوقيت: 00:04
*/
الطبقة العامة غير الآمنة {
public static void main(String[] args) {
مهمة UnsafeTask = New UnsafeTask();
لـ (int i = 0; i < 10; i++) {
موضوع الموضوع = موضوع جديد (مهمة)؛
Thread.start();
يحاول {
TimeUnit.SECONDS.sleep(2);
} قبض على (InterruptedException e) {
printStackTrace();
}
}
}
}
الكود الكامل لفئة SafeTask:
انسخ رمز الكود كما يلي:
الحزمة com.diguage.books.concurrencycookbook.chapter1.recipe9؛
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* استخدم المتغيرات المحلية للخيط لضمان سلامة الخيط
* التاريخ: 2013-09-29
*الزمن: 23:34
*/
الطبقة العامة SafeTask تنفذ Runnable {
خاص ثابت ThreadLocal<Date> startDate = جديد
الموضوع المحلي<التاريخ>() {
@تجاوز
التاريخ المحمي القيمة الأولية () {
إرجاع تاريخ جديد ()؛
}
};
@تجاوز
تشغيل الفراغ العام () {
System.out.printf ("بدء الموضوع: %s : %s/n"،
Thread.currentThread().getId(), startDate.get());
يحاول {
TimeUnit.SECONDS.sleep((int) Math.rint(Math.random() * 10));
} قبض على (InterruptedException e) {
printStackTrace();
}
System.out.printf ("انتهى الموضوع: %s : %s/n"،
Thread.currentThread().getId(), startDate.get());
}
}
الكود الكامل لفئة SafeMain:
انسخ رمز الكود كما يلي:
الحزمة com.diguage.books.concurrencycookbook.chapter1.recipe9؛
import java.util.concurrent.TimeUnit;
/**
* مثال على الخيط الآمن
* التاريخ: 2013-09-24
*التوقيت: 00:04
*/
الطبقة العامة SafeMain {
public static void main(String[] args) {
مهمة SafeTask = SafeTask الجديدة ()؛
لـ (int i = 0; i < 10; i++) {
موضوع الموضوع = موضوع جديد (مهمة)؛
Thread.start();
يحاول {
TimeUnit.SECONDS.sleep(2);
} قبض على (InterruptedException e) {
printStackTrace();
}
}
}
}