當一個線程更新一個變量時,程序如果沒有正確的同步,那麼這個變量對於其他線程來說是不可見的。我們通常使用synchronized或者volatile來保證線程安全的更新共享變量。在JDK1.5中,提供了java.util.concurrent.atomic包,這個包中的原子操作類提供了一種用法簡單,性能高效,線程安全地更新一個變量的方式。
Atomic包裏一共提供了13個類,有4種類型的原子更新方式:原子更新基本類型、原子更新數組、原子更新引用和原子更新屬性。其實現基本都是使用Unsafe實現的包裝類。
1.原子更新基本類型類
- AtomicBoolean:原子更新布爾類型
- AtomicInteger:原子更新整型
- AtomicLong:原子更新長整型
以上3個類提供的方法基本一直,我們以AtomicInteger爲例進行分析。
AtomicInteger常用的方法有:
- int addAndGet(int delta):以原子方式將輸入的數值與實例中的值相加,並返回結果。
- boolean compareAndSet(int expect,int upate):如果輸入的數值等於預期值,則以原子方式將該值設置爲輸入的值。
- int getAndIncrement():以原子方式將當前值加1,返回自增前的值。
- void lazySet(int newValue):最終會設置成new Value,但可能導致其他線程在之後的一小段時間內還是可以讀到舊的值。
- int getAndSet(int newValue):以原子方式設置爲newValue,並返回舊值。
實例一:
package com.lipeng.seventh;
import java.util.concurrent.atomic.AtomicInteger;
/**
*
* 多線程更新atomicIntegerDemo
* @author LiPeng
*
*/
public class AtomicIntegerDemo {
static AtomicInteger atomicInteger=new AtomicInteger();
public static void main(String[] args) {
Thread thread1=new Thread(new TestAction(atomicInteger));
Thread thread2=new Thread(new TestAction(atomicInteger));
thread1.start();
thread2.start();
}
}
class TestAction implements Runnable{
AtomicInteger atomicInteger;
public TestAction(AtomicInteger atomicInteger) {
super();
this.atomicInteger = atomicInteger;
}
@Override
public void run() {
for(int i=0;i<10;++i){
System.out.println(Thread.currentThread().getName()+" exec incrementAndGet menthod[i="+i+"] and new value:"+atomicInteger.getAndIncrement());
}
}
}
其實現依靠我們熟悉的CAS算法:
在Java的基本類型中除了Atomic包中提供原子更新的基本類型外,還由char、float和double。那麼這些在Atomic包中沒有提供原子更新的基本類型怎麼保證其原子更新呢?
從AtomicBoolean源碼中我們可以得到答案:首先將Boolean轉換爲整型,然後使用comareAndSwapInt進行CAS,所以原子更新char、float、double同樣可以以此實現。
原子更新數組
- AtomicIntegerArray:原子更新整型數組裏的元素。
- AtomicLongArray:原子更新長整型數組裏的元素。
- AtomicReferenceArray:原子更新引用類型數組裏的元素。
【備註:】看書上說原子更新數組有4個類,除了上述3個外,還有AtomicBooleanArray類,但我在jdk5/6/7/8中都沒有找到這個類的存在,只找到共12個原子操作類,而不是標題中的13個。不知道是否是書中的錯誤?請知情的童鞋不吝賜教。
AtomicIntegerArray類主要提供原子的方式更新數組裏的整型,其常用方法如下:
- int addAndGet(int i,int delta):以原子方式將輸入值與數組中索引i的元素相加。
- boolean compareAndSet(int i,int expect,int update):如果當前值等於預期值,就把索引i的元素設置成update值。
【備註】:在AtomicIntegerArray構造方法中,AtomicIntegerArray會將數組複製一份,所以當其對內數組元素進行修改時,不會影響原傳入數組。
原子更新引用類型
原子更新基本類型的AtomicInteger,只能更新一個變量,如果需要原子更新多個變量,就需要使用這個原子更新引用類型提供的類。Atomic包提供以下3個類:
- AtomicReference:原子更新引用變量。
- AtomicReferenceFieldUpdater:原子更新引用類型裏的字段。
- AtomicMarkableReference:原子更新帶有標記位的引用類型 。可以原子更新一個布爾類型的標記位和引用類型。構造方法時AtomicMarkableReference(V initialRef,boolean initialMark)。
實例一:
package com.lipeng.seventh;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* AtomicReferenceDemo
* @author LiPeng
*
*/
public class AtomicReferenceDemo {
static AtomicReference<User> atomicReference=new AtomicReference<User>();
public static void main(String[] args) {
User user=new User("1","zhangsan");
//將user設置進去
atomicReference.set(user);
User newUser=new User("2","lisi");
//cas更新atomicReference裏user的引用
atomicReference.compareAndSet(user, newUser);
System.out.println(atomicReference.get().toString());
}
static class User{
private String userId;
private String userName;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public User(String userId, String userName) {
super();
this.userId = userId;
this.userName = userName;
}
@Override
public String toString() {
return "User [userId=" + userId + ", userName=" + userName + "]";
}
}
}
實例二:
package com.lipeng.seventh;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
/**
* AtomicReferenceFieldUpdaterDemo
* 原子更新User類中userName字段,此字段必須定義爲public且volatile
* @author LiPeng
*
*/
public class AtomicReferenceFieldUpdaterDemo {
static AtomicReferenceFieldUpdater<User, String> atomicFiled=AtomicReferenceFieldUpdater.newUpdater(User.class, String.class, "userName");
public static void main(String[] args) {
User user=new User("1","zhangsan");
atomicFiled.compareAndSet(user, user.userName, "lisi");
System.out.println(atomicFiled.get(user));
}
static class User{
public String userId;
public volatile String userName;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public User(String userId, String userName) {
super();
this.userId = userId;
this.userName = userName;
}
@Override
public String toString() {
return "User [userId=" + userId + ", userName=" + userName + "]";
}
}
}
原子更新字段類
如果需要原子的更新某個類裏的某個字段,就需要使用原子更新字段類,Atomic包提供了以下3個類進行原子字段更新:
- AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
- AtomicLongFiledUpdater:原子更新長整型字段的更新器。
- AtomicStampedReference:原子更新帶有版本號的引用類型。該類將整數值與引用關聯起來,用於原子的更新數據和數據的版本號,避免CAS的ABA問題、
想要原子的更新字段類需要調用靜態方法newUpdater()創建一個更新器,並設置想要更新的類和屬性。且更新類的字段必須使用public volatile修飾符。
實例一:
package com.lipeng.seventh;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
/**
* AtomicIntegerFieldUpdaterDemo
* 原子更新user中age字段
* @author LiPeng
*
*/
public class AtomicIntegerFieldUpdaterDemo {
static AtomicIntegerFieldUpdater<User> atomicFiledUpdater=AtomicIntegerFieldUpdater.newUpdater(User.class,"age");
public static void main(String[] args) {
User user=new User("zhangsan",24);
//age 自增一歲
atomicFiledUpdater.incrementAndGet(user);
System.out.println(atomicFiledUpdater.get(user));
}
static class User{
public String name;
public volatile int age;
public User(String name, int age) {
super();
this.name = name;
this.age = age;
}
}
}