AtomicInteger ב-Java

היום נסתכל על AtomicInteger ב-Java. פעולות אטומיות מתבצעות ביחידה יחידה של משימה בלתי מעורבת מפעולות אחרות. פעולות אטומיות הן נחוצות בסביבה מרובת תהליכים כדי למנוע אי עקביות בנתונים.

AtomicInteger

בואו ניצור תוכנית מרובה תהליכים פשוטה שבה כל תהליך מגדיל את משתנה ה-count המשותף 4 פעמים. אז אם ישנם שני תהליכים, לאחר שהם יסיימו, ערך ה-count ישוב להיות 8. JavaAtomic.java

package com.journaldev.concurrency;

public class JavaAtomic {

    public static void main(String[] args) throws InterruptedException {

        ProcessingThread pt = new ProcessingThread();
        Thread t1 = new Thread(pt, "t1");
        t1.start();
        Thread t2 = new Thread(pt, "t2");
        t2.start();
        t1.join();
        t2.join();
        System.out.println("Processing count=" + pt.getCount());
    }

}

class ProcessingThread implements Runnable {
    private int count;

    @Override
    public void run() {
        for (int i = 1; i < 5; i++) {
            processSomething(i);
            count++;
        }
    }

    public int getCount() {
        return this.count;
    }

    private void processSomething(int i) {
        // מעבד משימה מסוימת
        try {
            Thread.sleep(i * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

אם תפעיל את התוכנית לעיל, תשים לב שערך ה-count ישתנה בין 5, 6, 7, 8. הסיבה היא ש-count++ אינה פעולה אטומית. כך שבזמן שתהליך אחד קורא לערך ומעלה אותו באחד, תהליך אחר יכול לקרוא לערך הישן יותר, מה שמוביל לתוצאה שגויה. כדי לפתור את הבעיה הזו, עלינו לוודא שפעולת ההגדלה על ה-count היא אטומית. נוכל לעשות זאת באמצעות סנכרון, אך Java 5 מספקת מחלקות אריזה ב-java.util.concurrent.atomic עבור int ו-long שניתן להשתמש בהן כדי להשיג את פעולת ההגדלה האטומית הזו ללא שימוש בסנכרון.

דוגמה ל־Java AtomicInteger

כאן התוכנית המעודכנת שתחזיר תמיד את ערך המונה כ־8 מאחר ו־AtomicInteger באמצעות השיטה incrementAndGet() מגבילה כולם ובאופן אטומי את הערך הנוכחי באחד.

package com.journaldev.concurrency;

import java.util.concurrent.atomic.AtomicInteger;

public class JavaAtomic {

    public static void main(String[] args) throws InterruptedException {

        ProcessingThread pt = new ProcessingThread();
        Thread t1 = new Thread(pt, "t1");
        t1.start();
        Thread t2 = new Thread(pt, "t2");
        t2.start();
        t1.join();
        t2.join();
        System.out.println("Processing count=" + pt.getCount());
    }
}

class ProcessingThread implements Runnable {
    private AtomicInteger count = new AtomicInteger();

    @Override
    public void run() {
        for (int i = 1; i < 5; i++) {
            processSomething(i);
            count.incrementAndGet();
        }
    }

    public int getCount() {
        return this.count.get();
    }

    private void processSomething(int i) {
        // עיבוד עבודה מסוימת
        try {
            Thread.sleep(i * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

היתרונות של שימוש במחלקות Concurrency לפעולה אטומית הם שאין צורך לדאוג לסנכרון. זה משפר קריאות של הקוד ומפחית את הסיכוי לשגיאות. גם פעולות קונקורנטיות אטומיות מניחות כי הן יעילות יותר מסנכרון שמשתמש בנעילת משאבים.

Source:
https://www.digitalocean.com/community/tutorials/atomicinteger-java