تعدد المسالك Multi-Threading في لغة الجافا – القسم الرابع

تحقيق التزامن بين الخيوط

لاحظنا في آخر مثال عند تكرار التنفيذ ظهور نتائج غير مستقرة، والسبب في ذلك أنه لا يوجد أي نوع من التنسيق(التزامن) بين الخيوط، فكل من الخيطين لا يعلم بوجود الآخر ويقوم بوصول غير آمن للمتحول count فمن الناحية النظرية عند تكرار تنفيذ البرنامج قد نصادف لحظة ما يقوم فيها الخيطان بالتعديل على المتحول معاً مما يؤدي لنتائج غير متوقعة (في الحقيقة قمت بتكرار التنفيذ عدد كبير من المرات حتى حصلت في إحداها على نهاية التنفيذ عند الرقم 18 بدل الرقم 20)، والسؤال كيف يمكن تحقيق وصول آمن للمتحولات من قبل الخيوط.

الكلمة المحجوزة synchronized

تؤمن هذه الكلمة وصولاً آمناً للمتحولات عبر منع أكثر من خيط من الوصول إلى متغير في نفس اللحظة.

مثال للتوضيح

ليكن لدينا الصف Counter المعرف بالشكل التالي:

package com.mutasemhajhasan.threading.example2;

/**
 *
 * @author Mutasem
 */
public class Counter {

    private int count;

    public synchronized int getCount() {
        return count;
    }


    Public synchronized void increment() throws InterruptedException {
        System.out.println("Count bfore loop: " + count);
        for (int i = 0; i < 10; i++) {
            count++;
            Thread.sleep(100);
        }
        System.out.println("Count after loop: " + count);
    }

}

يحوي هذا الصف على عداد بقيمة ابتدائية صفر، ودالة increment مسؤولة عن زيادة هذا العداد بمقدار 10 ودالة لقراءة قيمة العداد get.

ولمنع وصول أكثر من خيط في نفس الوقت للمتحول count قمنا بإضافة الكلمة synchronized قبل الدالة getCount المسؤولة عن القراءة من المتحول، وأيضاً قبل الدالة increment المسؤولة عن عملية الزيادة بالتالي لايمكن إلا لخيط واحد فقط القيام بعملية القراءة أو الكتابة على المتحول count في لحظة معينة.

لو قمنا بإنشاء كائن من الصف Counter وإنشاء خيطين، وظيفة كل منهما استدعاء الدالة increment لنفس الكائن بالشكل التالي:

package com.mutasemhajhasan.threading.example2;

import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Mutasem
 */
public class MyThread extends Thread {

    Counter c;

    public MyThread(Counter c) {
        this.c = c;
    }

    @Override
    public void run() {
        try {
            c.increment();
        } catch (InterruptedException ex) {
            Logger.getLogger(MyThread.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

}

package com.mutasemhajhasan.threading.example2;

/**
 *
 * @author Mutasem
 */
public class MainClass {

    public static void main(String[] args) throws InterruptedException {
        Counter c = new Counter();
        MyThread t1 = new MyThread(c);
        MyThread t2 = new MyThread(c);
        t1.start();
        Thread.sleep(200);
        System.out.println("counter " + c.getCount());
        t2.start();
        Thread.sleep(200);
        System.out.println("counter " + c.getCount());
        Thread.sleep(2000);
        System.out.println("counter " + c.getCount());

    }
}

ثم لنقم بتنفيذ البرنامج سنحصل دائماً على خرج مستقر هو التالي:

Count bfore loop: 0
Count after loop: 10
counter 10
Count bfore loop: 10
Count after loop: 20
counter 20
counter 20

مع نهاية هذا المثال أكون قد أنهيت القسم المعقد من تعدد الخيوط وأرجو أن أكون قد وفقت في إيصال هذا المفهوم.

رابط السلسة على GitHub

https://github.com/mutasemhajhasan/Java-Multi-Threading

المراجع

Herbert Schildt. (2014). Java: the Complete Reference 9th Edition. New York: McGraw-Hill Education.