0Pricing
Java Academy · درس

التدفقات المتوازية: الأداء والمشكلات

فعّل التدفقات المتوازية، وافهم مجموعة مؤشرات الترابط المشتركة، وتجنّب أخطاء الحالة القابلة للتغيير المشتركة

التدفقات المتوازية: الأداء والمشكلات درس مجاني في Java Academy على CoddyKit. هذا هو الدرس 2 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Java Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Java Academy 4 دروس في المجموع.

تفعيل التدفقات المتوازية

استدعِ .parallel() على أي تدفق، أو استخدم Collection.parallelStream(). تُنفَّذ عمليات التدفق على ForkJoinPool المشترك (الافتراضي: عدد نوى وحدة المعالجة المركزية ناقص 1 من مؤشرات الترابط).

long count = list.parallelStream()
    .filter(n -> n % 2 == 0)
    .count();
System.out.println(count);

متى يكون التوازي مفيدًا

تكون التدفقات المتوازية مفيدة عندما تكون مجموعة البيانات كبيرة (100,000 عنصر أو أكثر)، وتكون عملية كل عنصر مكلفة حسابيًا، ويكون المسار عديم الحالة وغير معتمد على الترتيب.

// Good candidate: CPU-heavy computation on large dataset
long sum = LongStream.rangeClosed(1, 10_000_000)
    .parallel()
    .filter(n -> isPrime(n))
    .sum();
System.out.println(sum);

متى لا تُستخدم التدفقات المتوازية

تجنبوا التدفقات المتوازية مع المجموعات الصغيرة، أو العمليات المقيدة بالإدخال والإخراج (لأنها تحجب مؤشرات ترابط ForkJoinPool)، أو العمليات المعتمدة على الحالة (مثل الفرز وdistinct)، أو عندما يكون الترتيب مهمًا وتكون استعادته مكلفة.

// Bad: I/O bound — blocking ForkJoinPool threads starves other tasks
List<String> result = urls.parallelStream()
    .map(url -> httpGet(url)) // BLOCKS the common pool
    .collect(Collectors.toList());

خطأ الحالة المشتركة القابلة للتغيير

تُنفَّذ عمليات التدفقات المتوازية على مؤشرات ترابط متعددة في الوقت نفسه. يؤدي تعديل مجموعة مشتركة قابلة للتغيير إلى حالات تسابق على البيانات ونتائج غير صحيحة.

// RACE CONDITION — never do this:
List<Integer> results = new ArrayList<>(); // not thread-safe
numbers.parallelStream().forEach(n -> results.add(n)); // corrupts list!
// Fix:
List<Integer> safe = numbers.parallelStream().collect(Collectors.toList());

العمليات المعتمدة على الحالة: sorted وdistinct

تتطلب sorted() وdistinct() رؤية جميع العناصر قبل إنتاج المخرجات، مما يحد من التوازي وغالبًا ما يجعل التنفيذ المتوازي أبطأ من التنفيذ التسلسلي.

// sorted() forces collect-all, then sort — parallel overhead usually not worth it:
list.parallelStream().sorted().collect(Collectors.toList());

العمليات الحساسة للترتيب

يحافظ findFirst() وforEachOrdered() على ترتيب الوصول في التدفقات المتوازية، مما يضيف تكلفة للمزامنة. استخدموا findAny() أو forEach() عندما لا يكون الترتيب مهمًا.

// Faster in parallel (order-insensitive):
Optional<Integer> any = list.parallelStream().filter(n -> n > 10).findAny();
// Slower in parallel (must preserve order):
Optional<Integer> first = list.parallelStream().filter(n -> n > 10).findFirst();

اختيار حجم تجمع مؤشرات الترابط

يستخدم ForkJoinPool المشترك مؤشرات الترابط التي يساوي عددها Runtime.getRuntime().availableProcessors() - 1. شغّلوا التدفقات المتوازية على تجمع مخصص من خلال إرسالها داخل استدعاء ForkJoinPool.invoke().

ForkJoinPool custom = new ForkJoinPool(8);
long result = custom.submit(
    () -> LongStream.rangeClosed(1, 1_000_000).parallel().sum()
).get();

قياس أداء التوازي مقارنةً بالتسلسل

احرصوا دائمًا على قياس الأداء باستخدام JMH وبأحجام بيانات واقعية. لا تكون تكلفة التوازي (التقسيم، وتنسيق مؤشرات الترابط، والدمج) مبررة إلا عندما يتجاوز وقت الحساب هذه التكلفة بكثير.

@Benchmark
public long sequential() { return LongStream.rangeClosed(1,1_000_000).sum(); }
@Benchmark
public long parallel()   { return LongStream.rangeClosed(1,1_000_000).parallel().sum(); }

عمليات الاختزال مع التوازي

صُممت reduce() وcollect() للعمل بشكل صحيح بالتوازي عندما تكون العمليات تجميعية وتكون قيمة الهوية صحيحة.

// Associative reduce — safe in parallel:
int sum = list.parallelStream().reduce(0, Integer::sum);
// Non-associative: subtraction — NOT safe in parallel:
int bad = list.parallelStream().reduce(0, (a, b) -> a - b); // wrong result!

قابلية التقسيم مهمة

تقسم التدفقات المتوازية مصدر البيانات باستخدام Spliterator. تُقسَّم ArrayList والمصفوفات بتعقيد O(1)، بينما تُقسَّم LinkedList وHashSet بشكل سيئ، مما يقلل كفاءة التوازي.

ملخص: قائمة التحقق من التدفقات المتوازية

قبل استخدام التوازي، تحققوا من توفر ما يلي: (1) مجموعة بيانات كبيرة، (2) عمليات مقيدة بوحدة المعالجة المركزية، (3) عدم وجود حالة مشتركة قابلة للتغيير، (4) عدم حساسية للترتيب، (5) مصدر قابل للتقسيم (مصفوفة أو ArrayList). عند الشك، قيسوا الأداء.

تحقق سريع

ماذا يحدث عند الإضافة إلى مجموعة غير آمنة للاستخدام مع مؤشرات الترابط داخل forEach لتدفق متوازٍ؟

مراجعة

تستخدم التدفقات المتوازية ForkJoinPool. وتكون فعالة مع المسارات الكبيرة والمقيدة بوحدة المعالجة المركزية وغير الحساسة للترتيب وعديمة الحالة. لا تعدّلوا الحالة المشتركة أبدًا. قيسوا الأداء قبل اعتمادها — فالتوازي غالبًا أبطأ مع البيانات الصغيرة.

الأسئلة الشائعة

هل درس «التدفقات المتوازية: الأداء والمشكلات» مجاني؟

نعم — نص درس «التدفقات المتوازية: الأداء والمشكلات» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Java Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Java Academy 4 دروس في المجموع.

ماذا ستتعلم في «التدفقات المتوازية: الأداء والمشكلات»؟

فعّل التدفقات المتوازية، وافهم مجموعة مؤشرات الترابط المشتركة، وتجنّب أخطاء الحالة القابلة للتغيير المشتركة تتمرن على Java Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Java Academy؟

لا تُشترط خبرة سابقة. Java Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 2 من أصل 4.

كم من الوقت يستغرق درس «التدفقات المتوازية: الأداء والمشكلات»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Java Academy هذا؟

نعم. كل درس في Java Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. ‏flatMap للمجموعات المتداخلة
  2. التدفقات المتوازية: الأداء والمشكلات
  3. ‏Spliterator: التقسيم لتحقيق التوازي
  4. التدفقات اللانهائية باستخدام iterate وgenerate
← العودة إلى Java Academy