مقدمة: من الأدوات الأساسية synchronized إلى الأدوات الحديثة
جافا هي واحدة من اللغات القليلة التي تحتوي على دعم مدمج لتعدد الخيوط منذ نشأتها. ومع ذلك، على المستوى الأساسي، غالبًا ما يقتصر المطورون على الكلمة المفتاحية synchronized و Thread البسيطة. في الأنظمة الحقيقية عالية التحميل، هذا غير كافٍ: تؤدي الأقفال إلى حالات الجمود (deadlocks)، ويؤدي إنشاء الخيوط بشكل ساذج إلى تحميل زائد على JVM.
في هذه المقالة، سنستعرض الآليات المتقدمة للتزامن في جافا التي تتيح كتابة كود متعدد الخيوط فعال وقابل للتوسع وآمن. المادة موجهة للمطورين الذين لديهم معرفة مسبقة بأساسيات تعدد الخيوط (الشاشات (monitors)، wait/notify، volatile).
سنغطي أربعة مواضيع رئيسية: إطار Fork/Join للمهام التكرارية، CompletableFuture للبرمجة غير المتزامنة، StampedLock كبديل لـ ReadWriteLock، والاستخدام الصحيح لـ ThreadLocal.
1. إطار Fork/Join: التقسيم التكراري للمهام
ظهر إطار Fork/Join في جافا 7 وهو مصمم للاستخدام الفعال للمعالجات متعددة النوى عند حل المهام التي يمكن تقسيمها إلى مهام فرعية (فرق تسد). الفئة الرئيسية هي ForkJoinPool، والتي تستخدم آلية "سرقة المهام" (work-stealing).
1.1. مبدأ العمل
بدلاً من إنشاء الخيوط يدويًا، تصف المهمة كوريث لـ RecursiveTask (إذا كانت هناك قيمة مرتجعة) أو RecursiveAction (بدون قيمة مرتجعة). ثم تقوم بتقسيم المهمة بشكل تكراري إلى أجزاء حتى تصبح صغيرة بما يكفي للتنفيذ المباشر.
1.2. مثال: جمع مصفوفة بالتوازي
import java.util.concurrent.RecursiveTask;import java.util.concurrent.ForkJoinPool;
public class SumTask extends RecursiveTask<Long> { private static final int THRESHOLD = 10_000; private final int[] array; private final int start, end;
public SumTask(int[] array, int start, int end) { this.array = array; this.start = start; this.end = end; }
@Override protected Long compute() { if (end - start <= THRESHOLD) { long sum = 0; for (int i = start; i < end; i++) { sum += array[i]; } return sum; } int mid = start + (end - start) / 2; SumTask left = new SumTask(array, start, mid); SumTask right = new SumTask(array, mid, end); left.fork(); // تشغيل الجزء الأيسر بشكل غير متزامن long rightResult = right.compute(); // حساب الجزء الأيمن في الخيط الحالي long leftResult = left.join(); // انتظار نتيجة الجزء الأيسر return leftResult + rightResult; }
public static void main(String[] args) { int[] data = new int[1_000_000]; for (int i = 0; i < data.length; i++) data[i] = i;
ForkJoinPool pool = ForkJoinPool.commonPool(); long result = pool.invoke(new SumTask(data, 0, data.length)); System.out.println("المجموع: " + result); }}هام: استخدم ForkJoinPool.commonPool() - فهو مُحسَّن بالفعل لعدد النوى. تجنب إنشاء مجمعات خاصة بك دون داعٍ.
2. CompletableFuture: البرمجة غير المتزامنة بدون جحيم الاستدعاءات الراجعة (callback)
قبل جافا 8، كانت البرمجة غير المتزامنة ممكنة عبر Future، لكنه كان متزامنًا (استدعاء get() حاجب). CompletableFuture يحل هذه المشكلة من خلال توفير واجهة برمجة تطبيقات وظيفية قوية لتكوين العمليات غير المتزامنة.
2.1. السيناريوهات الأساسية
يمكنك إنشاء CompletableFuture، وتشغيل المهام بشكل غير متزامن، وربطها في سلاسل:
import java.util.concurrent.CompletableFuture;
public cl