1. 程式人生 > >Java 原子操作類詳解(AtomicInteger、AtomicIntegerArray等)

Java 原子操作類詳解(AtomicInteger、AtomicIntegerArray等)

當程式更新一個變數時,如果多執行緒同時更新這個變數,可能得到期望之外的值,比如變數i=1,A執行緒更新i+1,B執行緒也更新i+1,經過兩個執行緒操作之後可能i不等於3,而是等於2。因為A和B執行緒在更新變數i的時候拿到的i都是1,這就是執行緒不安全的更新操作,通常我們會使用synchronized來解決這個問題,synchronized會保證多執行緒不會同時更新變數i。

import java.util.concurrent.CountDownLatch;

public class UnSafeAdd {
	private static int threadCount=10;
	private static CountDownLatch countDown=new CountDownLatch(threadCount);
	private static int count=0;
	private static class Counter implements Runnable{ 
		@Override
		public void run() {
			for(int i=0;i<1000;i++){
				count++;//非原子操作
			} 
			countDown.countDown();
		} 
	}
	public static void main(String[] args) throws InterruptedException {
		Thread[] threads=new Thread[threadCount];
		for(int i=0;i<threadCount;i++){
			threads[i]=new Thread(new Counter());
		}
		for(int i=0;i<threadCount;i++){
			threads[i].start();;
		}
		countDown.await();
		System.out.println(count);
	}
輸出:

8968

import java.util.concurrent.CountDownLatch;


public class SafeAddWithSyn {
	private static int threadCount=10;
	private static CountDownLatch countDown=new CountDownLatch(threadCount);
	private static int count=0;
	synchronized private static void addCount(){//同步方法
		count++;
	}
	private static class Counter implements Runnable{ 
		@Override
		public void run() {
			for(int i=0;i<1000;i++){
				addCount();
			} 
			countDown.countDown();
		} 
	}
	public static void main(String[] args) throws InterruptedException {
		Thread[] threads=new Thread[threadCount];
		for(int i=0;i<threadCount;i++){
			threads[i]=new Thread(new Counter());
		}
		for(int i=0;i<threadCount;i++){
			threads[i].start();;
		}
		countDown.await();
		System.out.println(count);
	}
}
輸出:

10000

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

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
 
public class SafeAddWithAtomicInteger {
	private static int threadCount=10;
	private static CountDownLatch countDown=new CountDownLatch(threadCount);
	private static AtomicInteger count=new AtomicInteger(0);//原子操作類
	private static class Counter implements Runnable{ 
		@Override
		public void run() {
			for(int i=0;i<1000;i++){
				count.addAndGet(1);
			} 
			countDown.countDown();
		} 
	}
	public static void main(String[] args) throws InterruptedException {
		Thread[] threads=new Thread[threadCount];
		for(int i=0;i<threadCount;i++){
			threads[i]=new Thread(new Counter());
		}
		for(int i=0;i<threadCount;i++){
			threads[i].start();;
		}
		countDown.await();
		System.out.println(count.get());
	}
}
輸出:

10000

原子更新基本型別

使用原子的方式更新基本型別,Atomic包提供了以下3個類。
AtomicBoolean:原子更新布林型別。
AtomicInteger:原子更新整型。
AtomicLong:原子更新長整型。
以上3個類提供的方法幾乎一模一樣,所以本節僅以AtomicInteger為例進行講解,AtomicInteger的原始碼如下:

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates 使用Unsafe類的CAS操作實現更新
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;
    ......
    //volatile保證可見性
    private volatile int value;
    //構造器
    public AtomicInteger(int initialValue) {
        value = initialValue;
    }
    public AtomicInteger() {
    }
    ......

    //CAS更新
    public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
    ......
    /*以下方法都使用迴圈CAS更新資料*/
<pre name="code" class="java">    public final int getAndSet(int newValue) {//以原子方式設定新值
        for (;;) {
            int current = get();
            if (compareAndSet(current, newValue))
                return current;
        }
    }
 public final int getAndIncrement() {//以原子方式自增 for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } } ......
//以原子方式將輸入值與當前值相加並返回結果 public final int addAndGet(int delta) { for (;;) {//迴圈 int current = get(); int next = current + delta; if (compareAndSet(current, next))//CAS return next; } } ......}

原子更新陣列

通過原子的方式更新數組裡的某個元素,Atomic包提供了以3類
AtomicIntegerArray:原子更新整型數組裡的元素。
AtomicLongArray:原子更新長整型數組裡的元素。
AtomicReferenceArray:原子更新引用型別數組裡的元素。

import java.util.concurrent.CountDownLatch;

public class AtomicIntegerArrayTest {
	private static int threadCount=1000;
	private static CountDownLatch countDown=new CountDownLatch(threadCount);
	static int[] values=new int[10];
	private static class Counter implements Runnable{ 
		@Override
		public void run() { 
			 for(int i=0;i<100;i++){
				 for(int j=0;j<10;j++){//所有元素+1
					 values[j]++;
				 }
			 }
			 countDown.countDown();
		} 
	} 
	public static void main(String[] args) throws InterruptedException{
		Thread[] threads=new Thread[threadCount];
		for(int i=0;i<threadCount;i++){
			threads[i]=new Thread(new Counter());
		}
		for(int i=0;i<threadCount;i++){
			threads[i].start();;
		}
		countDown.await();
		for(int i=0;i<10;i++){
			System.out.print(values[i]+" ");
		}
	}
}
輸出:

99997 99996 99997 99997 99996 99996 99996 99996 99996 99996

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicIntegerArray;

public class AtomicIntegerArrayTest {
	private static int threadCount=1000;
	private static CountDownLatch countDown=new CountDownLatch(threadCount);
	static int[] values=new int[10];
	static AtomicIntegerArray ai=new AtomicIntegerArray(values);
	private static class Counter implements Runnable{ 
		@Override
		public void run() { 
			 for(int i=0;i<100;i++){
				 for(int j=0;j<10;j++){//所有元素+1
					 ai.getAndIncrement(j); 
				 }
			 }
			 countDown.countDown();
		} 
	} 
	public static void main(String[] args) throws InterruptedException{
		Thread[] threads=new Thread[threadCount];
		for(int i=0;i<threadCount;i++){
			threads[i]=new Thread(new Counter());
		}
		for(int i=0;i<threadCount;i++){
			threads[i].start();;
		}
		countDown.await();
		for(int i=0;i<10;i++){
			System.out.print(ai.get(i)+" ");
		}
		System.out.println();
		for(int i=0;i<10;i++){
			System.out.print(values[i]+" ");
		}
	}
}
輸出:

100000 100000 100000 100000 100000 100000 100000 100000 100000 100000
0 0 0 0 0 0 0 0 0 0
需要注意的是,陣列value通過構造方法傳遞進去,然後AtomicIntegerArray會將當前陣列複製一份,所以當AtomicIntegerArray對內部的陣列元素進行修改時,不會影響傳入的陣列。

//An {@code int} array in which elements may be updated atomically.
public class AtomicIntegerArray implements java.io.Serializable {
    ......
    private final int[] array;//儲存資料的int陣列

    ......
    //構造器
    public AtomicIntegerArray(int length) {
        array = new int[length];
    }
    public AtomicIntegerArray(int[] array) {
        // Visibility guaranteed by final field guarantees
        this.array = array.clone();//這裡會複製傳入的int陣列,因此不會改變原來的int陣列
    }

    public final int length() {
        return array.length;
    }
    ......
}

原子更新引用

原子更新基本型別的AtomicInteger,只能更新一個變數,如果要原子更新多個變數,就需要使用這個原子更新引用型別提供的類。Atomic包提供了以下3個類。
AtomicReference:原子更新引用型別。
AtomicReferenceFieldUpdater:原子更新引用型別裡的欄位。
AtomicMarkableReference:原子更新帶有標記位的引用型別。

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicReference;

public class Test { 
	private static int threadCount=10;
	private static CountDownLatch countDown=new CountDownLatch(threadCount);
	public static AtomicReference<User> atomicUserRef = new AtomicReference<User>();
	private static class ReferenceUpdater implements Runnable{ 
		User user;
		public ReferenceUpdater(User user){
			this.user=user;
		}
		@Override
		public void run() {
			for(int i=0;i<1000;i++){ 
				User oldUser=atomicUserRef.get(); 
				atomicUserRef.compareAndSet(oldUser, user);
				Thread.yield();
			} 
			countDown.countDown();
		} 
	}
	public static void main(String[] args) throws InterruptedException {
		Thread[] threads=new Thread[threadCount];
		for(int i=0;i<threadCount;i++){
			threads[i]=new Thread(new ReferenceUpdater(new User("name"+i,i)));
		}
		for(int i=0;i<threadCount;i++){
			threads[i].start();;
		}
		countDown.await(); 
		System.out.println(atomicUserRef.get().getName());
		System.out.println(atomicUserRef.get().getOld());
	}

	static class User {
		private String name;
		private int old;

		public User(String name, int old) {
			this.name = name;
			this.old = old;
		}

		public String getName() {
			return name;
		}

		public int getOld() {
			return old;
		}
	}
}

輸出:

name1
1
每次輸出結果都不確定,10種情況都有可能,但是name屬性和age屬性是匹配的。

/**An object reference that may be updated atomically.*/
public class AtomicReference<V>  implements java.io.Serializable {
<pre name="code" class="java">    ......
private static final Unsafe unsafe = Unsafe.getUnsafe();//用於CAS更新 ...... private volatile V value;//引用,volatile保證可見性 public AtomicReference(V initialValue) { value = initialValue; } public AtomicReference() { } //利用Unsafe類執行CAS操作 public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); } ......
//迴圈CAS public final V getAndSet(V newValue) { while (true) { V x = get(); if (compareAndSet(x, newValue)) return x; } }
......}

原子更新欄位

如果需原子地更新某個類裡的某個欄位時,就需要使用原子更新欄位類,Atomic包提供了以下3個類進行原子欄位更新。
AtomicIntegerFieldUpdater:原子更新整型的欄位的更新器。
AtomicLongFieldUpdater:原子更新長整型欄位的更新器。
AtomicStampedReference:原子更新帶有版本號的引用型別。該類將整數值與引用關聯起來,可用於原子的更新資料和資料的版本號,可以解決使用CAS進行原子更新時可能出現的ABA問題。

要想原子地更新欄位類需要兩步。第一步,因為原子更新欄位類都是抽象類,每次使用的時候必須使用靜態方法newUpdater()建立一個更新器,並且需要設定想要更新的類和屬性。第二步,更新類的欄位(屬性)必須使用public volatile修飾符。

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
 
public class AtomicIntegerFieldUpdaterTest { 
	// 建立原子更新器,並設定需要更新的物件類和物件的屬性
	private static AtomicIntegerFieldUpdater<User> a = 
			AtomicIntegerFieldUpdater.newUpdater(User.class, "old");
 
	public static void main(String[] args) throws InterruptedException {  
		// 設定柯南的年齡是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

內容源自:

《Java併發程式設計的藝術》