البرمجة غير المتزامنة في Java: دليل كامل للمبتدئين
\n\nيجب أن تكون التطبيقات الحديثة سريعة وسريعة الاستجابة. عندما ينفذ البرنامج عملية طويلة (قراءة ملف، طلب إلى قاعدة بيانات، أو استدعاء API خارجي)، فإن الكود المتزامن التقليدي يحجب الخيط الرئيسي، مما يجبر المستخدم على الانتظار. تسمح البرمجة غير المتزامنة في Java بحل هذه المشكلة عن طريق تشغيل المهام في خيوط خلفية دون حجب خيط التنفيذ الرئيسي. في هذا الدليل، سنشرح أساسيات عدم التزامن في Java، بدءًا من المفاهيم البسيطة وصولاً إلى الأمثلة العملية.
\n\nما هي البرمجة غير المتزامنة ولماذا نحتاجها؟
\n\nفي البرمجة المتزامنة، يتم تنفيذ الكود بشكل تسلسلي: كل سطر ينتظر انتهاء السطر السابق. إذا واجهت عملية إدخال/إخراج (I/O)، فإن البرنامج "ينام" أثناء تنفيذها. تسمح البرمجة غير المتزامنة بتشغيل العملية دون انتظار اكتمالها، ومتابعة تنفيذ المهام الأخرى. عندما تكون النتيجة جاهزة، يتلقى البرنامج إشعارًا ويعالجها.
\n\nالمزايا الرئيسية للنهج غير المتزامن:
\n- \n
- زيادة الأداء: يمكن للتطبيق معالجة آلاف الطلبات المتزامنة دون إنشاء آلاف الخيوط. \n
- استجابة واجهة المستخدم: لا تتجمد الواجهة الرسومية عند تنفيذ المهام الثقيلة. \n
- توفير الموارد: لا تبقى الخيوط خاملة في انتظار I/O، بل تُستخدم لأعمال أخرى. \n
في Java، يتم تنفيذ عدم التزامن من خلال تعدد الخيوط، ولكن مع الأدوات الحديثة مثل CompletableFuture، أصبحت إدارته أسهل وأكثر أمانًا.
الأدوات الرئيسية للبرمجة غير المتزامنة في Java
\n\nتوفر Java عدة مستويات من التجريد للعمل مع عدم التزامن. دعنا نستعرض أهمها، بدءًا من المستوى الأكثر أساسية.
\n\n1. فئة Thread وواجهة Runnable
\nالطريقة الأساسية هي إنشاء خيط جديد باستخدام فئة Thread أو تنفيذ واجهة Runnable. ومع ذلك، يتطلب هذا النهج إدارة يدوية للخيوط، مما قد يؤدي إلى أخطاء وإهدار الموارد.
public class SimpleAsyncExample {\n public static void main(String[] args) {\n // الطريقة 1: إنشاء خيط عبر فئة Runnable مجهولة\n Thread thread = new Thread(new Runnable() {\n @Override\n public void run() {\n System.out.println("المهمة غير المتزامنة تُنفذ في الخيط: " \n + Thread.currentThread().getName());\n }\n });\n thread.start();\n\n // الطريقة 2: باستخدام تعبير لامدا (Java 8+)\n Thread lambdaThread = new Thread(() -> {\n System.out.println("خيط لامدا: " + Thread.currentThread().getName());\n });\n lambdaThread.start();\n\n System.out.println("الخيط الرئيسي يواصل العمل: " \n + Thread.currentThread().getName());\n }\n}\n\n2. ExecutorService ومجموعات الخيوط
\nلإدارة الخيوط، من الأفضل استخدام ExecutorService. فهو يوفر مجموعة من الخيوط التي يتم إعادة استخدامها لتنفيذ المهام. هذا أكثر كفاءة من إنشاء خيط جديد لكل مهمة.
import java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\npublic class ExecutorServiceExample {\n public static void main(String[] args) {\n // إنشاء مجموعة من 4 خيوط\n ExecutorService executor = Executors.newFixedThreadPool(4);\n\n // إرسال المهام للتنفيذ\n for (int i = 0; i < 10; i++) {\n int taskId = i;\n executor.submit(() -> {\n System.out.println("المهمة " + taskId + " تُنفذ في الخيط: " \n + Thread.currentThre