假定有兩個操做A和B,若是從執行A的線程來看,當另外一個線程執行B時,要麼將B所有執行完,要麼徹底不執行B,那麼A和B對彼此來講是原子的java
實現原子操做可使用鎖,鎖機制,知足基本的需求是沒有問題的了,可是有的時候咱們的需求並不是這麼簡單,咱們須要更有效,更加靈活的機制,synchronized
關鍵字是基於阻塞的鎖機制(鎖升級),也就是說當一個線程擁有鎖的時候,訪問同一資源的其它線程須要等待,直到該線程釋放鎖程序員
這裏會有些問題:首先,若是被阻塞的線程優先級很高很重要怎麼辦?其次,若是得到鎖的線程一直不釋放鎖怎麼辦?(這種狀況是很是糟糕的)。還有一種狀況,若是有大量的線程來競爭資源,那CPU將會花費大量的時間和資源來處理這些競爭,同時,還有可能出現一些例如死鎖之類的狀況,最後,其實鎖機制是一種比較粗糙,粒度比較大的機制,相對於像計數器這樣的需求有點兒過於笨重算法
實現原子操做還可使用當前的處理器基本都支持CAS()
的指令,只不過每一個廠家所實現的算法並不同,每個CAS操做過程都包含三個運算符:數組
操做的時候若是這個地址上存放的值等於這個指望的值A,則將地址上的值賦爲新值B,不然不作任何操做安全
CAS的基本思路就是,若是這個地址上的值和指望的值相等,則給其賦予新值,不然不作任何事兒,可是要返回原值是多少。循環CAS就是在一個循環裏不斷的作cas操做,直到成功爲止
架構
CAS是怎麼實現線程的安全呢:ide
語言層面不作處理,咱們將其交給硬件—CPU和內存,利用CPU的多處理能力,實現硬件層面的阻塞,再加上volatile變量的特性便可實現基於原子操做的線程安全
由於CAS須要在操做值的時候,檢查值有沒有發生變化,若是沒有發生變化則更新,可是若是一個值原來是A,變成了B,又變成了A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,可是實際上卻變化了優化
ABA問題的解決思路就是使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加1,那麼A→B→A
就會變成1A→2B→3A
。舉個通俗點的例子,你倒了一杯水放桌子上,幹了點別的事,而後同事把你水喝了又給你從新倒了一杯水,你回來看水還在,拿起來就喝,若是你無論水中間被人喝過,只關心水還在,這就是ABA問題。this
若是你是一個講衛生講文明的小夥子,不但關心水在不在,還要在你離開的時候水被人動過沒有,由於你是程序員,因此就想起了放了張紙在旁邊,寫上初始值0,別人喝水前麻煩先作個累加才能喝水atom
ABA問題只是一種現象,並不必定是問題,在架構設計中也有這種樂觀鎖的使用(冪等性操做)
自旋CAS若是長時間不成功,會給CPU帶來很是大的執行開銷
當對一個共享變量執行操做時,咱們可使用循環CAS的方式來保證原子操做,可是對多個共享變量操做時,循環CAS就沒法保證操做的原子性,這個時候就能夠用鎖
還有一個取巧的辦法,就是把多個共享變量合併成一個共享變量來操做。好比,有兩個共享變量i=2,j=a,合併一下ij=2a,而後用CAS來操做ij。從Java 1.5開始,JDK提供了AtomicReference
類來保證引用對象之間的原子性,就能夠把多個變量放在一個對象裏來進行CAS操做
示例代碼:
/** * 解決一樣的問題的更高效的方法,使用AtomXXX類 * AtomXXX類自己方法都是原子性的,但不能保證多個方法連續調用是原子性的 */ import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class T01_AtomicInteger { /*volatile*/ //int count1 = 0; AtomicInteger count = new AtomicInteger(0); /*synchronized*/ void m() { for (int i = 0; i < 10000; i++) //if count1.get() < 1000 count.incrementAndGet(); //count1++; } public static void main(String[] args) { T01_AtomicInteger t = new T01_AtomicInteger(); List<Thread> threads = new ArrayList<Thread>(); for (int i = 0; i < 10; i++) { threads.add(new Thread(t::m, "thread-" + i)); } threads.forEach((o) -> o.start()); threads.forEach((o) -> { try { o.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); System.out.println(t.count); } }
運行結果:
100000
上面例子能夠看出用AtomicInteger,和加synchronized鎖效果是同樣的,並且CAS效率更高
除了synchronized、AtomicInteger以外還有LongAdder,下面代碼比較一下他們各自的效率:
import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.LongAdder; public class T02_AtomicVsSyncVsLongAdder { static long count2 = 0L; static AtomicLong count1 = new AtomicLong(0L); static LongAdder count3 = new LongAdder(); public static void main(String[] args) throws Exception { Thread[] threads = new Thread[1000]; for(int i=0; i<threads.length; i++) { threads[i] = new Thread(()-> { for(int k=0; k<100000; k++) count1.incrementAndGet(); }); } long start = System.currentTimeMillis(); for(Thread t : threads ) t.start(); for (Thread t : threads) t.join(); long end = System.currentTimeMillis(); //TimeUnit.SECONDS.sleep(10); System.out.println("Atomic: " + count1.get() + " time " + (end-start)); //----------------------------------------------------------- Object lock = new Object(); for(int i=0; i<threads.length; i++) { threads[i] = new Thread(new Runnable() { @Override public void run() { for (int k = 0; k < 100000; k++) synchronized (lock) { count2++; } } }); } start = System.currentTimeMillis(); for(Thread t : threads ) t.start(); for (Thread t : threads) t.join(); end = System.currentTimeMillis(); System.out.println("Sync: " + count2 + " time " + (end-start)); //---------------------------------- for(int i=0; i<threads.length; i++) { threads[i] = new Thread(()-> { for(int k=0; k<100000; k++) count3.increment(); }); } start = System.currentTimeMillis(); for(Thread t : threads ) t.start(); for (Thread t : threads) t.join(); end = System.currentTimeMillis(); //TimeUnit.SECONDS.sleep(10); System.out.println("LongAdder: " + count1.longValue() + " time " + (end-start)); } static void microSleep(int m) { try { TimeUnit.MICROSECONDS.sleep(m); } catch (InterruptedException e) { e.printStackTrace(); } } }
運行結果:
Atomic: 100000000 time 2255 Sync: 100000000 time 3257 LongAdder: 100000000 time 509
主要是提供原子的方式更新數組裏的整型,其經常使用方法以下:
須要注意的是:
數組value經過構造方法傳遞進去,而後
AtomicIntegerArray
會將
當前數組複製一份,因此當AtomicIntegerArray對內部的數組元素進行修改時,不會影響傳入的數組
示例代碼:
import java.util.concurrent.atomic.AtomicIntegerArray; /** *類說明: */ public class AtomicArray { static int[] value = new int[] { 1, 2 }; static AtomicIntegerArray ai = new AtomicIntegerArray(value); public static void main(String[] args) { ai.getAndSet(0, 3); System.out.println(ai.get(0)); System.out.println(value[0]);//原數組不會變化 } }
運行結果:
3 1
原子更新基本類型的AtomicInteger
,只能更新一個變量,若是要原子更新多個變量,就須要使用這個原子更新引用類型提供的類。Atomic包提供瞭如下3個類
原子更新引用類型
示例代碼:
/** *類說明:演示引用類型的原子操做類 */ public class UseAtomicReference { static AtomicReference<UserInfo> atomicUserRef; public static void main(String[] args) { //要修改的實體的實例 UserInfo user = new UserInfo("Mark", 15); atomicUserRef = new AtomicReference(user); UserInfo updateUser = new UserInfo("Bill",17); atomicUserRef.compareAndSet(user,updateUser); System.out.println(atomicUserRef.get()); System.out.println(user); } //定義一個實體類 static class UserInfo { private volatile String name; private int age; public UserInfo(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } @Override public String toString() { return "UserInfo{" + "name='" + name + '\'' + ", age=" + age + '}'; } } }
運行結果:
UserInfo{name='Bill', age=17} UserInfo{name='Mark', age=15}
LongAdder > Atomic > synchronized | | | 分段鎖+CAS CAS 普通鎖 線程多優點大 分段線程加CAS 最後把結果合起來
利用版本戳的形式記錄了每次改變之後的版本號,這樣的話就不會存在ABA問題了。這就是AtomicStampedReference
的解決方案。AtomicMarkableReference
跟AtomicStampedReference
差很少, AtomicStampedReference
是使用pair
的int stamp
做爲計數器使用,AtomicMarkableReference
的pair
使用的是boolean mark
。 仍是那個水的例子,AtomicStampedReference
可能關心的是動過幾回,AtomicMarkableReference
關心的是有沒有被人動過,方法都比較簡單
示例代碼:
import java.util.concurrent.atomic.AtomicStampedReference; /** *類說明:演示帶版本戳的原子操做類 */ public class UseAtomicStampedReference { static AtomicStampedReference<String> asr = new AtomicStampedReference("mark",0); public static void main(String[] args) throws InterruptedException { //拿到當前的版本號(舊) final int oldStamp = asr.getStamp(); final String oldReference = asr.getReference(); System.out.println(oldReference+"============"+oldStamp); Thread rightStampThread = new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+":當前變量值:" +oldReference + "-當前版本戳:" + oldStamp + "-" + asr.compareAndSet(oldReference, oldReference + "+Java", oldStamp, oldStamp + 1)); } }); Thread errorStampThread = new Thread(new Runnable() { @Override public void run() { String reference = asr.getReference(); System.out.println(Thread.currentThread().getName() +":當前變量值:" +reference + "-當前版本戳:" + asr.getStamp() + "-" + asr.compareAndSet(reference, reference + "+C", oldStamp, oldStamp + 1)); } }); rightStampThread.start(); rightStampThread.join(); errorStampThread.start(); errorStampThread.join(); System.out.println(asr.getReference()+"============"+asr.getStamp()); } }
運行結果:
mark============0 Thread-0:當前變量值:mark-當前版本戳:0-true Thread-1:當前變量值:mark+Java-當前版本戳:1-false mark+Java============1
原子更新帶有標記位的引用類型。能夠原子更新一個布爾類型的標記位和引用類型。構造方法是AtomicMarkableReference(V initialRef,booleaninitialMark)
若是需原子地更新某個類裏的某個字段時,就須要使用原子更新字段類,Atomic包提供瞭如下3個類進行原子字段更新
要想原子地更新字段類須要兩步。第一步,由於原子更新字段類都是抽象類,每次使用的時候必須使用靜態方法newUpdater()建立一個更新器,而且須要設置想要更新的類和屬性。第二步,更新類的字段(屬性)必須使用public volatile修飾符
原子更新整型的字段的更新器
原子更新長整型字段的更新器
原子更新引用類型裏的字段