سلامة الخيوط والآثار الجانبية
تجنب الحالة القابلة للتغيير المشتركة
سلامة الخيوط والآثار الجانبية درس مجاني في Java Academy على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Java Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Java Academy 4 دروس في المجموع.
التوازي يعني الوصول المتزامن
في التدفق المتوازي، تنفّذ خيوط متعددة دوال lambda الخاصة بك في الوقت نفسه. وأي حالة مشتركة قابلة للتغيير تلمسها هذه الدوال تصبح حالة تسابق.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
int safe = IntStream.rangeClosed(1, 1000).parallel().sum();
System.out.println(safe);
}
}الخطر: تغيير متغير مشترك
يفقد عدّاد int عادي تلمسه خيوط متعددة بعض التحديثات. لا تجمع أبدًا في حالة مشتركة قابلة للتغيير داخل forEach.
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
AtomicInteger counter = new AtomicInteger();
IntStream.rangeClosed(1, 1000).parallel()
.forEach(n -> counter.incrementAndGet());
System.out.println(counter.get());
}
}لماذا تفشل المجموعات العادية
قد يؤدي الإضافة إلى ArrayList غير الآمن للخيوط من خلال forEach متوازٍ إلى إفسادها أو فقدان بعض العناصر. والحل هو عدم المشاركة والتغيير على الإطلاق.
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
List<Integer> result = IntStream.rangeClosed(1, 10).parallel()
.boxed()
.collect(Collectors.toList());
System.out.println(result.size());
}
}الأداة الصحيحة: collect
استخدم collect بدلًا من forEach ذي الآثار الجانبية. إذ يجمع إطار عمل أدوات التجميع الحاويات الجزئية بأمان لكل خيط، ثم يدمجها.
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
List<Integer> squares = IntStream.rangeClosed(1, 6).parallel()
.map(n -> n * n)
.boxed()
.collect(Collectors.toList());
System.out.println(squares);
}
}الأداة الصحيحة: reduce
يكون reduce آمنًا بطبيعته مع الخيوط عندما تكون العملية تجميعية وعديمة الحالة، لأنه يدمج النتائج الجزئية دون تغيير حالة مشتركة.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
int sum = IntStream.rangeClosed(1, 1000).parallel()
.reduce(0, Integer::sum);
System.out.println(sum);
}
}دوال lambda عديمة الحالة
يجب أن تكون دوال lambda في التدفقات عديمة الحالة؛ أي أن تعتمد نتيجتها على الإدخال فقط، لا على متغيرات خارجية قابلة للتغيير أو على العناصر السابقة.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
long count = IntStream.rangeClosed(1, 1000).parallel()
.filter(n -> n % 2 == 0)
.count();
System.out.println(count);
}
}الأنواع الذرية كحل بديل
إذا كان لا بد فعلًا من مشاركة عدّاد، فاستخدم فئات ذرية مثل AtomicLong. فهي تضمن تسلسل التحديثات بشكل صحيح، لكنها تضيف تزاحمًا.
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
AtomicLong sum = new AtomicLong();
IntStream.rangeClosed(1, 1000).parallel()
.forEach(sum::addAndGet);
System.out.println(sum.get());
}
}المجموعات المتزامنة
عندما تحتاج إلى خريطة مشتركة بين الخيوط، يكون ConcurrentHashMap آمنًا. ومع ذلك، فضّل Collectors.groupingByConcurrent حيثما أمكن.
import java.util.Map;
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<String> words = List.of("a", "bb", "cc", "ddd");
Map<Integer, List<String>> byLen = words.parallelStream()
.collect(Collectors.groupingByConcurrent(String::length));
System.out.println(byLen);
}
}تجنّب المنطق المعتمد على الترتيب
لا تعتمد على ترتيب الوصول داخل دوال lambda المتوازية. فالعمليات التي تفترض المعالجة التسلسلية تنتج نتائج خاطئة عند تنفيذها بشكل متوازٍ.
import java.util.stream.IntStream;
public class Main {
public static void main(String[] args) {
int sum = IntStream.rangeClosed(1, 100).parallel()
.map(n -> n * 2)
.sum();
System.out.println(sum);
}
}تتوازى الدوال الخالصة بأمان
القاعدة الذهبية: اكتب عمليات خالصة وعديمة الحالة وغير متداخلة. عندها يمكن أن يكون التدفق تسلسليًا أو متوازيًا مع الحصول على نتائج متطابقة وصحيحة.
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<String> in = List.of("red", "green", "blue");
String out = in.parallelStream()
.map(String::toUpperCase)
.sorted()
.collect(Collectors.joining(","));
System.out.println(out);
}
}عدم التداخل
لا تعدّل مجموعة المصدر أثناء تشغيل تدفق عليها. فهذا يسبب ConcurrentModificationException أو سلوكًا غير محدد، سواء كان التنفيذ تسلسليًا أم متوازيًا.
import java.util.List;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Integer> src = List.of(1, 2, 3, 4);
List<Integer> doubled = src.parallelStream()
.map(n -> n * 2)
.collect(Collectors.toList());
System.out.println(doubled);
}
}تحقق سريع
ما الطريقة الموصى بها لتجميع النتائج من تدفق متوازٍ؟
مراجعة
لقد حافظت على صحة التدفقات المتوازية:
- تؤدي الحالة المشتركة القابلة للتغيير في دوال lambda المتوازية إلى حالات تسابق.
- فضّل
collectوreduceعلىforEachذي الآثار الجانبية. - حافظ على أن تكون دوال lambda عديمة الحالة وخالصة وغير متداخلة.
- إذا كان لا بد من المشاركة، فاستخدم الأنواع الذرية أو المجموعات المتزامنة، لكن اجعل ذلك خيارك الأخير.
الأسئلة الشائعة
هل درس «سلامة الخيوط والآثار الجانبية» مجاني؟
نعم — نص درس «سلامة الخيوط والآثار الجانبية» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Java Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Java Academy 4 دروس في المجموع.
ماذا ستتعلم في «سلامة الخيوط والآثار الجانبية»؟
تجنب الحالة القابلة للتغيير المشتركة تتمرن على Java Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Java Academy؟
لا تُشترط خبرة سابقة. Java Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.
كم من الوقت يستغرق درس «سلامة الخيوط والآثار الجانبية»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Java Academy هذا؟
نعم. كل درس في Java Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- إنشاء التدفقات المتوازية
- متى تفيد المعالجة المتوازية
- سلامة الخيوط والآثار الجانبية
- الأخطاء الشائعة