Java中的原子類

主要參考《實戰java高併發程序設計》、《併發編程的藝術》,感覺學習這13個原子類,還是要自己動手寫一些,試一試,才能理解它們的用法和不同之處。

1.原子類的引入以及AtomicInteger

首先看一個例子:

public class AtomicNoIntegerDemo {
    static int i = 0;
    public static class AddThread implements Runnable{
        @Override
        public void run(){
            for(int k=0;k<10000;k++)
                i++;
        }
    }

    public static void main(String[] args) throws InterruptedException{
        Thread[] ts = new Thread[10];
        for(int k=0;k<10;k++){
            ts[k] = new Thread(new AddThread());
        }
        for(int k=0;k<10;k++) {
            ts[k].start();
        }
        for(int k=0;k<10;k++){
            ts[k].join();
        }
        System.out.println(i);
    }
}

一個普通的int變量,以上面的代碼相加,最後的值可能不是100000,因爲10個線程共同對i做加法,但是i++的過程不是原子操作,會出現線程不安全的可能。可以按照下面的方法修改,將i++加鎖,每次只能有一個線程進行i++,這樣就能保證最終i的值爲100000。 

        public void run(){
            for(int k=0;k<10000;k++)
                synchronized(AtomicNoIntegerDemo.class) {
                    i++;
                }
        }

這樣做是使用加鎖的方式,這樣做效率會比較低,而無鎖是一種樂觀的策略,它使用CAS(Compare And Swap)策略來鑑別線程衝突,一旦檢測到衝突產生,就重試當前操作直到沒有衝突爲止。

CAS優勢:

CAS:算法過程:

而原子類就是使用CAS來實現的,JDK有一個併發包atomic,裏面實現了一些直接使用CAS操作的線程安全的類型。上面那個例子可以使用AtomicTnteger來保證線程安全。

代碼如下,很好理解 

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicNoIntegerDemo {
    static AtomicInteger i = new AtomicInteger();
    public static class AddThread implements Runnable{
        @Override
        public void run(){
            for(int k=0;k<10000;k++)
                i.incrementAndGet();
        }
    }

    public static void main(String[] args) throws InterruptedException{
        Thread[] ts = new Thread[10];
        for(int k=0;k<10;k++){
            ts[k] = new Thread(new AddThread());
        }
        for(int k=0;k<10;k++) {
            ts[k].start();
        }
        for(int k=0;k<10;k++){
            ts[k].join();
        }
        System.out.println(i);
    }
}

2.無鎖的對象引用:AtomicReference

上面講述了AtomicInteger的用法,但如果一個對象,比如Student類的一個對象student,想要原子的更新等,那麼就不能用AtomicInteger了,這時候可以使用AtomicReference,利用泛型,實現各種類對象的原子操作,保證線程安全。一個例子如下所示。

public class JustTry {
    public static void main(String[] args) throws InterruptedException {
        //可以在定義student1時傳入一個Student對象作爲參數,也可以在下面的set時設置student1的值
        AtomicReference<Student> student1 = new AtomicReference<>();
        Student s1 = new Student("Tom",19);
        Student s2 = new Student("Tony",20);
        student1.set(s1);
        while (true){
            if(student1.compareAndSet(s1,s2)) break;
        }
        System.out.println(student1.get().getName());
        //可以看到student1的改變沒有影響s1
        System.out.println(s1.getName());
    }
    public static class Student{
        private int age;
        private String name;
        Student(String name,int age){
            this.name = name;
            this.age = age;
        }
        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

AtomicReference有一個問題,如果在CAS過程中,其他線程改變了這個變量,又改回去了,那麼CAS會照常進行,沒有反映,這在一些情況下沒有影響,但在一些情況下可能會造成很大的問題,所以需要一回人標誌位判斷這個變量是否改變過,這時AtomicStampedReference可以排上用場,注意,AtomicStampedReference定義時需要傳入泛型類的對象來初始化,CAS時要判斷標誌位是否變化,其他和AtomicReference區別不大。一個例子:

import java.util.concurrent.atomic.AtomicStampedReference;

public class AtomicStampedReferenceDemo {
    static AtomicStampedReference<Integer> money = new AtomicStampedReference<Integer>(19, 0);

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            final int timestamp = money.getStamp();
            new Thread() {

                public void run() {
                    while (true) {
                        while (true) {
                            Integer m = money.getReference();
                            if (m < 20) {
                                if (money.compareAndSet(m, m + 20, timestamp, timestamp + 1)) {
                                    System.out.println("餘額小於20元,充值成功,餘額:" + money.getReference() + "元");
                                    break;
                                }
                            } else {
//                                System.out.println("餘額大於20元,無需充值");
                                break;
                            }
                        }
                    }
                }
            }.start();
        }

        new Thread() {

            public void run() {
                for (int i = 0; i < 100; i++) {
                    while (true) {
                        int timestamp = money.getStamp();
                        Integer m = money.getReference();
                        if (m > 10) {
                            System.out.println("大於10元");
                            if (money.compareAndSet(m, m - 10, timestamp, timestamp + 1)) {
                                System.out.println("成功消費10元,餘額:" + money.getReference());
                                break;
                            }
                        } else {
                            System.out.println("沒有足夠的金額");
                            break;
                        }
                    }
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }

}

3.無鎖的數組 AtomicIntegerArray

無鎖的數組可以使用AtomicIntegerArray,可以使用這個類提供的方法,線程安全地對數組中的元素進行操作。

mport java.util.concurrent.atomic.AtomicIntegerArray;

public class AtomicIntegerArrayDemo {
    static AtomicIntegerArray arr = new AtomicIntegerArray(10);

    public static class AddThread implements Runnable {

        public void run() {
            for (int k = 0; k < 10000; k++)
                arr.getAndIncrement(k % arr.length());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread[] ts = new Thread[10];
        for (int k = 0; k < 10; k++) {
            ts[k] = new Thread(new AddThread());
        }
        for (int k = 0; k < 10; k++) {
            ts[k].start();
        }

        for (int k = 0; k < 10; k++) {
            ts[k].join();
        }
        System.out.println(arr);
    }
}

4. 對象中某個字段的原子操作:AtomicIntegerFieldUpdater

上面的AtomicReference只能保證原子地操作對象本身,而AtomicIntegerFieldUpdater可以原子地操作對象中的字段。下面代碼中Candidate,score和allSore總是相等,說明AtomicIntegerFieldUpdater保證了原子操作和線程安全。

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class AtomicIntegerFieldUpdaterDemo {
    public static class Candidate {
        int id;
        AtomicInteger score = new AtomicInteger(0);
    }

    public final static AtomicIntegerFieldUpdater<Candidate> scoreUpdater =
            AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");
    public static AtomicInteger allscore = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        final Candidate stu = new Candidate();
        Thread[] t = new Thread[10000];
        for (int i = 0; i < 10000; i++) {
            t[i] = new Thread() {

                public void run() {
                    if (Math.random() > 0.4) {
                        scoreUpdater.incrementAndGet(stu);
                        allscore.incrementAndGet();
                    }
                }
            };
            t[i].start();
        }
        for (int i = 0; i < 10000; i++) {
            t[i].join();
        }
        System.out.println("score=" + stu.score);
        System.out.println("allscore=" + allscore);
    }
}

注意點:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章