ما هو التزامن في جافا؟ الطريقة، الكتلة، النوع الثابت

ما هو التزامن في جافا؟

في Java، تشير المزامنة إلى القدرة على التحكم في وصول سلاسل رسائل متعددة إلى أي مورد مشترك. إنه خيار مثالي حيث نريد السماح لموضوع واحد فقط بالوصول إلى المورد المشترك.

يُشار عادةً إلى أسلوب التنفيذ هذا بالبرمجة "غير المتزامنة". هناك أيضًا مؤشرات ترابط في هذه المعالجات، وهي عمليات خفيفة الوزن يمكنها تنفيذ التعليمات في نفس الوقتneoعادة.

أنواع التزامن

هناك نوعان من طرق المزامنة في Java:

1) مزامنة العملية

2) مزامنة الموضوع.

دعونا ندرس الموضوع و مزامنة العملية بالتفصيل.

مزامنة العملية: يدير التزامن بين البرامج. على سبيل المثال، برامج مثل `Microsoft يتم تشغيل Word وAcrobat Reader كعمليتين فرديتين.

مزامنة الموضوع: يُطلق على التنفيذ المتزامن للمورد المهم بواسطة خيطين أو أكثر اسم تزامن الخيط. يمكن تجميعك بشكل أكبر للتواصل المتبادل والترابط.

ما هو القفل في جافا؟

تم إنشاء Lock in Java حول كيان داخلي يُعرف بالشاشة أو القفل. كل الكائنات لها قفل مرتبط بها. لذلك، يجب أن يحصل الخيط الذي يحتاج إلى وصول متسق إلى حقول الكائن على قفل الكائن قبل الوصول إليه، ويحرر القفل عند انتهاء العمل. وهذا يضمن وصول مؤشر ترابط واحد فقط إلى البيانات المشتركة في المرة الواحدة.

برنامج متعدد الخيوط مع المزامنة

برنامج متعدد الخيوط هي طريقة أو كتلة محمية من التداخل من سلاسل رسائل أخرى تشترك في نفس المورد المشار إليه باستخدام الكلمة الأساسية "المتزامنة".

باستخدام الطريقة المتزامنة

تُعرف أي طريقة يتم الإعلان عنها على أنها متزامنة باسم الطريقة المتزامنة. يتم استخدامه أيضًا لقفل كائن لأي مورد مشترك. لذلك، عندما يستدعي مؤشر ترابط طريقة متزامنة. يستحوذ تلقائيًا على قفل هذا الكائن ويحرره عندما ينهي مهمته.

ملحوظة: لا يمكن أن تعمل الكلمة الأساسية المتزامنة مع الفئات والمتغيرات. يمكن استخدام الأساليب والكتل فقط مع الكلمة الأساسية.

لماذا استخدام الطريقة المتزامنة؟

  • يتم استخدامه لقفل كائن لأي موارد مشتركة.
  • يحصل الكائن على القفل عند استدعاء الطريقة المتزامنة.
  • لا يتم تحرير القفل حتى يكمل الخيط وظيفته

بناء الجملة:

Acess_modifiers synchronized return_type method_name (Method_Parameters) {
}
class MathService {
    synchronized void getSumOfArray(int[] numbers) {
     int sum = 0;

         for (int number : numbers) {
             System.out.println(Thread.currentThread()
                     .getName()
                     + " adds "
                     + sum + " to "
                     + number + " to get -> "
                     + (sum += number));

             try {
                 Thread.sleep(500);
             } catch (InterruptedException e) {
                 throw new RuntimeException(e);
             }
         }
    }
}
public class Synchronization {
    public static void main(String[] args) {
        MathService mathService = new MathService();

        Thread threadOne = new Thread(() ->
                mathService.getSumOfArray(new int[]{10, 11, 12}));
        Thread threadTwo = new Thread(() ->
                mathService.getSumOfArray(new int[]{20, 21, 22}));

        threadOne.start();
        threadTwo.start();
    }
}

شرح الكود:

قم بتشغيل هذا المثال ولاحظ أن الخيط `0` يحصل على قفل كائن `mathService` أولاً ويستخدم هذا القفل حصريًا حتى يكتمل التنفيذ. لم يتم تشذير الخيط `0` و`1` في هذا الرمز. الإخراج كما هو موضح أدناه.

الإخراج:

Thread-0 adds 0 to 10 to get -> 10
Thread-0 adds 10 to 11 to get -> 21
Thread-0 adds 21 to 12 to get -> 33
Thread-1 adds 0 to 20 to get -> 20
Thread-1 adds 20 to 21 to get -> 41
Thread-1 adds 41 to 22 to get -> 63

باستخدام كتلة متزامنة

لنفترض أنك لا تريد مزامنة الطريقة بأكملها. بدلاً من ذلك، تريد مزامنة بضعة أسطر من التعليمات البرمجية. في ذلك الوقت، ساعدت الكتلة المتزامنة في مزامنة كود Java المحدد.

يتم الوصول إلى أقفال الطريقة المتزامنة على الطريقة، بينما يتم الوصول إلى أقفال الكتلة المتزامنة على الكائن.

class MathService {
    void getSumOfArray(int[] numbers) {
        synchronized (this){
            int sum = 0;

            for (int number : numbers) {
                System.out.println(Thread.currentThread()
                        .getName()
                        + " adds "
                        + sum + " to "
                        + number + " to get -> "
                        + (sum += number));

                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}
public class Synchronization {
    public static void main(String[] args) {
        MathService mathService = new MathService();

        Thread threadOne = new Thread(() ->
                mathService.getSumOfArray(new int[]{10, 11, 12}));
        Thread threadTwo = new Thread(() ->
                mathService.getSumOfArray(new int[]{20, 21, 22}));

        threadOne.start();
        threadTwo.start();
    }
}

شرح الكود:

عند تشغيل هذا الكود ستلاحظ أنه يعمل دون أي تدخل.

في الطريقة المتزامنة، يتم تطبيق القفل بواسطة الطريقة، ولكن في الكتلة المتزامنة، يتم تطبيق القفل بواسطة الكائن.

تأكد من أن الإخراج كما هو موضح أدناه.

الإخراج:

Thread-0 adds 0 to 10 to get -> 10
Thread-0 adds 10 to 11 to get -> 21
Thread-0 adds 21 to 12 to get -> 33
Thread-1 adds 0 to 20 to get -> 20
Thread-1 adds 20 to 21 to get -> 41
Thread-1 adds 41 to 22 to get -> 63

شرح الكود:

عند تشغيل هذا الكود ستلاحظ أنه يعمل بدون تشويش وهذا ما توقعناه. في الطريقة المتزامنة، يتم تطبيق القفل بواسطة الطريقة، ولكن في طريقة الكتلة المتزامنة، يتم تطبيق القفل بواسطة الكائن.

باستخدام المزامنة الثابتة

في مزامنة Java، إذا كان هناك أكثر من كائن واحد، فقد يحصل خيطان على الأقفال ويدخلان كتلة أو كتلة متزامنة، مع قفل منفصل لكل كائن. لتجنب ذلك، يمكن استخدام المزامنة الثابتة.

سيتم استخدام الكلمات الأساسية المتزامنة قبل الطرق الثابتة.

ملحوظة: في المزامنة الثابتة، يكون الوصول إلى القفل موجودًا في الفئة، وليس في الكائن أو الأسلوب.

رمز لتوضيح مشكلة قفل الكائنات المتعددة

class MathService {
    synchronized void getSumOfArray(int[] numbers) {
            int sum = 0;

            for (int number : numbers) {
                System.out.println(Thread.currentThread()
                        .getName()
                        + " adds "
                        + sum + " to "
                        + number + " to get -> "
                        + (sum += number));

                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
    }
}
public class Synchronization {
    public static void main(String[] args) {
        MathService mathService = new MathService();
        MathService mathService1 = new MathService();

        Thread threadOne = new Thread(() ->
                mathService.getSumOfArray(new int[]{10, 11, 12}));
        Thread threadTwo = new Thread(() ->
                mathService.getSumOfArray(new int[]{20, 21, 22}));
        Thread threadThree = new Thread(() ->
                mathService1.getSumOfArray(new int[]{10, 11, 12}));
        Thread threadFour = new Thread(() ->
                mathService1.getSumOfArray(new int[]{20, 21, 22}));

        threadOne.start();
        threadTwo.start();
        threadThree.start();
        threadFour.start();
    }
}

شرح الكود:

عندما نقوم بإنشاء مثيل آخر لـ "MathService"، فإننا نقدم تداخلًا في الخيوط حيث سيتم تشذيرها مع الكائنين. لاحظ أن الخيط `0` والخيط `2` مشذران مع الكائنين، في حين أن الخيط `1` و`3` مشذران مع الكائنين.

الإخراج:

Thread-0 adds 0 to 10 to get -> 10
Thread-2 adds 0 to 10 to get -> 10
Thread-0 adds 10 to 11 to get -> 21
Thread-2 adds 10 to 11 to get -> 21
Thread-0 adds 21 to 12 to get -> 33
Thread-2 adds 21 to 12 to get -> 33
Thread-1 adds 0 to 20 to get -> 20
Thread-3 adds 0 to 20 to get -> 20
Thread-1 adds 20 to 21 to get -> 41
Thread-3 adds 20 to 21 to get -> 41
Thread-1 adds 41 to 22 to get -> 63
Thread-3 adds 41 to 22 to get -> 63

نفس الرمز باستخدام طريقة ثابتة متزامنة

class MathService {
    synchronized static void getSumOfArray(int[] numbers) {
            int sum = 0;

            for (int number : numbers) {
                System.out.println(Thread.currentThread()
                        .getName()
                        + " adds "
                        + sum + " to "
                        + number + " to get -> "
                        + (sum += number));

                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
    }
}
public class Synchronization {
    public static void main(String[] args) {
        MathService mathService = new MathService();
        MathService mathService1 = new MathService();

        Thread threadOne = new Thread(() ->
                mathService.getSumOfArray(new int[]{10, 11, 12}));
        Thread threadTwo = new Thread(() ->
                mathService.getSumOfArray(new int[]{20, 21, 22}));
        Thread threadThree = new Thread(() ->
                mathService1.getSumOfArray(new int[]{10, 11, 12}));
        Thread threadFour = new Thread(() ->
                mathService1.getSumOfArray(new int[]{20, 21, 22}));

        threadOne.start();
        threadTwo.start();
        threadThree.start();
        threadFour.start();
    }
}

قم بتشغيل الكود أعلاه ولاحظ أننا قمنا الآن بإزالة تداخل الخيط. يظهر إخراج الكود أدناه.

الإخراج:

Thread-0 adds 0 to 10 to get -> 10
Thread-0 adds 10 to 11 to get -> 21
Thread-0 adds 21 to 12 to get -> 33
Thread-3 adds 0 to 20 to get -> 20
Thread-3 adds 20 to 21 to get -> 41
Thread-3 adds 41 to 22 to get -> 63
Thread-2 adds 0 to 10 to get -> 10
Thread-2 adds 10 to 11 to get -> 21
Thread-2 adds 21 to 12 to get -> 33
Thread-1 adds 0 to 20 to get -> 20
Thread-1 adds 20 to 21 to get -> 41
Thread-1 adds 41 to 22 to get -> 63

مزايا استخدام المزامنة

فيما يلي مزايا العمل مع التطبيقات المتزامنة:

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

عيوب آلية التزامن

آليات المزامنة لها أداء ضعيف.

مثلا

  • افترض أن هناك خمس عمليات، A1 وA2 وA3 وA4 وA5.
  • إنهم ينتظرون الموارد المشتركة للوصول إلى موضوع واحد في كل مرة.
  • تظل جميع العمليات في حالة انتظار، لذا يجب أن ينتظر الأخير في قائمة الانتظار حتى تكتمل جميع العمليات الأخرى.

نبذة عامة

  • تشير المزامنة إلى القدرة على التحكم في وصول سلاسل رسائل متعددة إلى أي مورد مشترك.
  • لدى Java نوعان من طرق المزامنة: 1) مزامنة العملية و 2) مزامنة الموضوع.
  • تم إنشاء Lock in Java حول كيان داخلي يُعرف بالشاشة أو القفل.
  • البرنامج متعدد الخيوط هو طريقة أو كتلة محمية من التداخل من سلاسل رسائل أخرى تشترك في نفس المورد المشار إليه باستخدام الكلمة الأساسية "المتزامنة".
  • تُعرف أي طريقة يتم الإعلان عنها على أنها متزامنة بأنها طريقة متزامنة.
  • في Java، يتم الوصول إلى أقفال الطريقة المتزامنة على الطريقة، بينما يتم الوصول إلى أقفال الكتل المتزامنة على الكائن.
  • في المزامنة الثابتة، يكون الوصول إلى القفل موجودًا في الفئة، وليس في الكائن أو الأسلوب.
  • الهدف الرئيسي للمزامنة في Java هو منع البيانات غير المتناسقة عن طريق منع تداخل الخيوط.
  • أكبر عيب في هذه الطريقة هو أن جميع العمليات تظل في حالة انتظار، لذا يجب أن تنتظر العملية الأخيرة في قائمة الانتظار حتى تكتمل جميع العمليات الأخرى.