Java并發編程-原子類及并發工具類

原子類

Java從JDK 1.5開始提供了java.util.concurrent.atomic包(以下簡稱Atomic包),這個包中
的原子操作類提供了一種用法簡單、性能高效、線程安全地更新一個變量的方式。
因為變量的類型有很多種,所以在Atomic包里一共提供了13個類,屬于4種類型的原子更
新方式,分別是原子更新基本類型、原子更新數組、原子更新引用和原子更新屬性(字段)。
Atomic包里的類基本都是使用Unsafe實現的包裝類

java.util.concurrent.atomic中的類可以分成4組:

  • 標量類(Scalar):AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
  • 數組類:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
  • 更新器類:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
  • 復合變量類:AtomicMarkableReference,AtomicStampedReference

CAS

CAS(Compare-And-Swap)算法保證數據操作的原子性。

CAS 算法是硬件對于并發操作共享數據的支持。

CAS 包含了三個操作數:
  內存值 V
  預估值 A
  更新值 B

當且僅當 V == A 時,V 將被賦值為 B,否則循環著不斷進行判斷 V 與 A 是否相等。

CAS的全稱為Compare-And-Swap,是一條CPU的原子指令,其作用是讓CPU比較后原子地更新某個位置的值,經過調查發現,其實現方式是基于硬件平臺的匯編指令,就是說CAS是靠硬件實現的,JVM只是封裝了匯編調用,那些AtomicInteger類便是使用了這些封裝后的接口。
atomic包的原子包下使用這種方案。例如AtomicInteger的getAndIncrement自增+1的方法,經查看源碼如下:

public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

源碼中for循環體的第一步先取得AtomicInteger里存儲的數值,第二步對AtomicInteger的當前數值進行加1操作,關鍵的第三步調用compareAndSet方法來進行原子更新操作,該方法先檢查當前數值是否等于current,等于意味著AtomicInteger的值沒有被其他線程修改過,則將AtomicInteger的當前數值更新成next的值,如果不等compareAndSet方法會返回false,程序會進入for循環重新進行compareAndSet操作

Unsafe只提供了3種CAS方法:compareAndSwapObject、compare-AndSwapInt和compareAndSwapLong,具體實現都是使用了native方法

/**
* 如果當前數值是expected,則原子的將Java變量更新成x
* @return 如果更新成功則返回true
*/
public final native boolean compareAndSwapObject(Object o,
long offset,
Object expected,
Object x);
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
public final native boolean compareAndSwapLong(Object o, long offset,
long expected,
long x);

標量類

  • AtomicBoolean:原子更新布爾變量
  • AtomicInteger:原子更新整型變量
  • AtomicLong:原子更新長整型變量
  • AtomicReference:原子更新引用類型

具體到每個類的源代碼中,提供的方法基本相同,這里以AtomicInteger為例進行說明
AtomicInteger源碼主要的方法如下,原理主要都要都是采用了CAS,這里不再累述

public class AtomicInteger extends Number implements java.io.Serializable {
   
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }
 //返回當前的值
    public final int get() {
        return value;
    }
    //最終會設置成新值
    public final void set(int newValue) {
        value = newValue;
    }
    
    public final void lazySet(int newValue) {
        unsafe.putOrderedInt(this, valueOffset, newValue);
    }
      //原子更新為新值并返回舊值
    public final int getAndSet(int newValue) {
        for (;;) {
            int current = get();
            if (compareAndSet(current, newValue))
                return current;
        }
    }
     //如果輸入的值等于預期值,則以原子方式更新為新值
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

 //原子自增
    public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
    }

//原子方式將當前值與輸入值相加并返回結果
    public final int getAndAdd(int delta) {
        for (;;) {
            int current = get();
            int next = current + delta;
            if (compareAndSet(current, next))
                return current;
        }
    }

 
}

數組類

  • AtomicIntegerArray:原子更新整型數組的某個元素
  • AtomicLongArray:原子更新長整型數組的某個元素
  • AtomicReferenceArray:原子更新引用類型數組的某個元素

AtomicIntegerArray類主要是提供原子的方式更新數組里的整型,其常用方法如下。

  • int addAndGet(int i,int delta):以原子方式將輸入值與數組中索引i的元素相加。
  • boolean compareAndSet(int i,int expect,int update):如果當前值等于預期值,則以原子方式將數組位置i的元素設置成update值。

以上幾個類提供的方法幾乎一樣,所以這里以AtomicIntegerArray為例進行講解

public class AtomicIntegerArray implements java.io.Serializable {
    private static final long serialVersionUID = 2862133569453604235L;

    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final int base = unsafe.arrayBaseOffset(int[].class);
    private static final int shift;
    private final int[] array;
 

    private long checkedByteOffset(int i) {
        if (i < 0 || i >= array.length)
            throw new IndexOutOfBoundsException("index " + i);

        return byteOffset(i);
    }

    private static long byteOffset(int i) {
        return ((long) i << shift) + base;
    }

     
    public AtomicIntegerArray(int length) {
        array = new int[length];
    }

   
    public final int get(int i) {
        return getRaw(checkedByteOffset(i));
    }

    private int getRaw(long offset) {
        return unsafe.getIntVolatile(array, offset);
    }

    
    public final void set(int i, int newValue) {
        unsafe.putIntVolatile(array, checkedByteOffset(i), newValue);
    }

   
    public final void lazySet(int i, int newValue) {
        unsafe.putOrderedInt(array, checkedByteOffset(i), newValue);
    }

    
    public final int getAndSet(int i, int newValue) {
        long offset = checkedByteOffset(i);
        while (true) {
            int current = getRaw(offset);
            if (compareAndSetRaw(offset, current, newValue))
                return current;
        }
    }
 
    public final boolean compareAndSet(int i, int expect, int update) {
        return compareAndSetRaw(checkedByteOffset(i), expect, update);
    }

    private boolean compareAndSetRaw(long offset, int expect, int update) {
        return unsafe.compareAndSwapInt(array, offset, expect, update);
    }

     

}

這里關鍵的實現也還是使用了CAS的策略,具體通過unsafe的native方法進行實現

原子更新字段類

如果需原子地更新某個類里的某個字段時,就需要使用原子更新字段類,Atomic包提供
了以下3個類進行原子字段更新。

  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
  • AtomicLongFieldUpdater:原子更新長整型字段的更新器。
  • AtomicStampedReference:原子更新帶有版本號的引用類型。該類將整數值與引用關聯起
    來,可用于原子的更新數據和數據的版本號,可以解決使用CAS進行原子更新時可能出現的
    ABA問題。
    要想原子地更新字段類需要兩步。第一步,因為原子更新字段類都是抽象類,每次使用的
    時候必須使用靜態方法newUpdater()創建一個更新器,并且需要設置想要更新的類和屬性。第
    二步,更新類的字段(屬性)必須使用public volatile修飾符
public class AtomicIntegerFieldUpdaterTest {
// 創建原子更新器,并設置需要更新的對象類和對象的屬性
private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.
newUpdater(User.class, "old");
public static void main(String[] args) {
// 設置柯南的年齡是10歲
User conan = new User("conan", 10);
// 柯南長了一歲,但是仍然會輸出舊的年齡
System.out.println(a.getAndIncrement(conan));
// 輸出柯南現在的年齡
System.out.println(a.get(conan));
}
public static class User {
private String name;
public volatile int old;
public User(String name, int old) {
this.name = name;
this.old = old;
}
public String getName() {
return name;
}
public int getOld() {
return old;
}
}
}

代碼執行后輸出如下。

10

11

并發工具類

CountDownLatch

CountDownLatch類是一個同步計數器,構造時默認接收一個初始值,每調用一次countDown()方法,計數器減1。計數器>0時,await()方法會阻塞;當計數器=0時會得到await()會立即得到響應
CountDownLatch類位于java.util.concurrent包下,利用它可以實現類似計數器的功能。比如有一個任務A,它要等待其他4個任務執行完畢之后才能執行,此時就可以利用CountDownLatch來實現這種功能了

public class Test {
     public static void main(String[] args) {   
         final CountDownLatch latch = new CountDownLatch(2);
          
         new Thread(){
             public void run() {
                 try {
                     System.out.println("子線程"+Thread.currentThread().getName()+"正在執行");
                    Thread.sleep(3000);
                    System.out.println("子線程"+Thread.currentThread().getName()+"執行完畢");
                    latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
             };
         }.start();
          
         new Thread(){
             public void run() {
                 try {
                     System.out.println("子線程"+Thread.currentThread().getName()+"正在執行");
                     Thread.sleep(3000);
                     System.out.println("子線程"+Thread.currentThread().getName()+"執行完畢");
                     latch.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
             };
         }.start();
          
         try {
             System.out.println("等待2個子線程執行完畢...");
            latch.await();
            System.out.println("2個子線程已經執行完畢");
            System.out.println("繼續執行主線程");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
     }

執行結果:

線程Thread-0正在執行
線程Thread-1正在執行
等待2個子線程執行完畢...
線程Thread-0執行完畢
線程Thread-1執行完畢
2個子線程已經執行完畢
繼續執行主線程

CyclicBarrier

CyclicBarrier的字面意思是可循環使用(Cyclic)的屏障(Barrier)。它要做的事情是,讓一組線程到達一個屏障(也可以叫同步點)時被阻塞,直到最后一個線程到達屏障時,屏障才會
開門,所有被屏障攔截的線程才會繼續運行。當所有等待線程都被釋放以后,CyclicBarrier可以被重用。CyclicBarrier類位于java.util.concurrent包下,CyclicBarrier提供2個構造器:

public CyclicBarrier(int parties, Runnable barrierAction) {
}
 
public CyclicBarrier(int parties) {
}

參數parties指讓多少個線程或者任務等待至barrier狀態;參數barrierAction為當這些線程都達到barrier狀態時會執行的內容

public class Test {
    public static void main(String[] args) {
        int N = 4;
        CyclicBarrier barrier  = new CyclicBarrier(N);
        for(int i=0;i<N;i++)
            new Writer(barrier).start();
    }
    static class Writer extends Thread{
        private CyclicBarrier cyclicBarrier;
        public Writer(CyclicBarrier cyclicBarrier) {
            this.cyclicBarrier = cyclicBarrier;
        }
 
        @Override
        public void run() {
            System.out.println("線程"+Thread.currentThread().getName()+"正在寫入數據...");
            try {
                Thread.sleep(5000);      //以睡眠來模擬寫入數據操作
                System.out.println("線程"+Thread.currentThread().getName()+"寫入數據完畢,等待其他線程寫入完畢");
                cyclicBarrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }catch(BrokenBarrierException e){
                e.printStackTrace();
            }
            System.out.println("所有線程寫入完畢,繼續處理其他任務...");
        }
    }
}

執行結果:

 線程Thread-0正在寫入數據...
線程Thread-3正在寫入數據...
線程Thread-2正在寫入數據...
線程Thread-1正在寫入數據...
線程Thread-2寫入數據完畢,等待其他線程寫入完畢
線程Thread-0寫入數據完畢,等待其他線程寫入完畢
線程Thread-3寫入數據完畢,等待其他線程寫入完畢
線程Thread-1寫入數據完畢,等待其他線程寫入完畢
所有線程寫入完畢,繼續處理其他任務...
所有線程寫入完畢,繼續處理其他任務...
所有線程寫入完畢,繼續處理其他任務...
所有線程寫入完畢,繼續處理其他任務...

Semaphore

Semaphore(信號量)是用來控制同時訪問特定資源的線程數量,它通過協調各個線程,以保證合理的使用公共資源。Semaphore可以控同時訪問的線程個數,通過acquire()獲取一個許可,如果沒有就等待,而 release() 釋放一個許可。

假若一個工廠有5臺機器,但是有8個工人,一臺機器同時只能被一個工人使用,只有使用完了,其他工人才能繼續使用。那么我們就可以通過Semaphore來實現:

public class Test {
    public static void main(String[] args) {
        int N = 8;            //工人數
        Semaphore semaphore = new Semaphore(5); //機器數目
        for(int i=0;i<N;i++)
            new Worker(i,semaphore).start();
    }
     
    static class Worker extends Thread{
        private int num;
        private Semaphore semaphore;
        public Worker(int num,Semaphore semaphore){
            this.num = num;
            this.semaphore = semaphore;
        }
         
        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println("工人"+this.num+"占用一個機器在生產...");
                Thread.sleep(2000);
                System.out.println("工人"+this.num+"釋放出機器");
                semaphore.release();           
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

執行結果:

工人0占用一個機器在生產...
工人1占用一個機器在生產...
工人2占用一個機器在生產...
工人4占用一個機器在生產...
工人5占用一個機器在生產...
工人0釋放出機器
工人2釋放出機器
工人3占用一個機器在生產...
工人7占用一個機器在生產...
工人4釋放出機器
工人5釋放出機器
工人1釋放出機器
工人6占用一個機器在生產...
工人3釋放出機器
工人7釋放出機器
工人6釋放出機器

Exchanger

Exchanger(交換者)是一個用于線程間協作的工具類。Exchanger用于進行線程間的數據交換。它提供一個同步點,在這個同步點,兩個線程可以交換彼此的數據。這兩個線程通過exchange方法交換數據,如果第一個線程先執行exchange()方法,它會一直等待第二個線程也執行exchange方法,當兩個線程都到達同步點時,這兩個線程就可以交換數據,將本線程生產出來的數據傳遞給對方。當在運行不對稱的活動時很有用。比如說,一個線程向buffer中填充數據,另一個線程從buffer中消費數據;這些線程可以用Exchange來交換數據。這個交換對于兩個線程來說都是全的

package com.clzhang.sample.thread;

import java.util.*;
import java.util.concurrent.Exchanger;

public class SyncExchanger {
    private static final Exchanger exchanger = new Exchanger();

    class DataProducer implements Runnable {
        private List list = new ArrayList();

        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println("生產了一個數據,耗時1秒");
                list.add(new Date());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            try {
                list = (List) exchanger.exchange(list);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (Iterator iterator = list.iterator(); iterator.hasNext();) {
                System.out.println("Producer " + iterator.next());
            }
        }
    }

    class DataConsumer implements Runnable {
        private List list = new ArrayList();

        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                list.add("這是一個收條。");
            }

            try {
                list = (List) exchanger.exchange(list);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for (Iterator iterator = list.iterator(); iterator.hasNext();) {
                Date d = (Date) iterator.next();
                System.out.println("Consumer: " + d);
            }
        }
    }

    public static void main(String[] args) {
        SyncExchanger ins = new SyncExchanger();
        new Thread(ins.new DataProducer()).start();
        new Thread(ins.new DataConsumer()).start();
    }
}

執行結果:

生產了一個數據,耗時1秒
生產了一個數據,耗時1秒
生產了一個數據,耗時1秒
生產了一個數據,耗時1秒
生產了一個數據,耗時1秒
Producer 這是一個收條。
Producer 這是一個收條。
Producer 這是一個收條。
Producer 這是一個收條。
Producer 這是一個收條。
Consumer: Thu Sep 12 17:21:39 CST 2013
Consumer: Thu Sep 12 17:21:40 CST 2013
Consumer: Thu Sep 12 17:21:41 CST 2013
Consumer: Thu Sep 12 17:21:42 CST 2013
Consumer: Thu Sep 12 17:21:43 CST 2013

參考資料

《Java并發編程的藝術》

Java并發編程:CountDownLatch、CyclicBarrier和Semaphore

Java并發編程系列之十九:原子操作類

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容