日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

无锁-1

發布時間:2024/4/13 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 无锁-1 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
我發現JDK當中的一些工具類,大量的都是用了無鎖的一些工具,其實我們已經有提到他的基本概念,首先無鎖呢,他就是無障礙的運行,它是指所有的線程,都能夠同時進入臨界區,但是無鎖在無障礙的基礎上呢,他增加了一條,我每一次競爭的時候呢,都能夠決定出一個優勝者,并不是大家都失敗,因此相對于無障礙來講呢,可能是一個更加切實可行的方案,但是事情也不是絕對這樣子的,因為根據有些理論來說呢,在實踐中,其實無障礙和無等待是比較接近的,只是從理論上來講,無障礙看起來呢,似乎線程有點出不去,但是實際上呢,運氣不會這么差,那么無鎖這個實現原理呢,是使用了CAS的指令,那么CAS是一個什么樣的思路呢,是說我現在要對這個數據進行一個賦值,這個數據本來是在臨界區當中,一個被保護的一個數據,那我要對她賦值,如果多個線程同時進來,我應該只有一個線程能夠成功的,那么我怎么判斷的,這個線程可以成功呢,他這里就要求說,線程在對數據進行操作的時候呢,你需要給出一個值,如果期望值和數據實際的值,是相符的,那么你就可以把這個數據設置下去,否則你就失敗,因為如果說你這個期望的數值,和他實際的數值不相符,那么我們就可能覺得呢,你這個數據,在你操作的限期當中呢,已經被其他線程給修改過了,因為這個數據已經被其他數據修改過了,所以你這一次就不能修改了,你要修改這個數據呢,要等到下一次去修改,CAS就是Compare And Swap,比較和交換,可以說每一次操作的時候呢,它會先去讀一下,讀一下當前值是多少,把這個期望值,讀出來的數據,跟你現在的數據做一個比較,CAS總是抱著一個樂觀的操作,完不成就重試,他認為這種概率是很小的,JAVA虛擬機的時候也介紹過無鎖的算法,CAS基本的思想,他是不是有一點小的bug在里面,因為你把一個值先去讀,比了之后你再設,如果說你這個數據讀出來的時候,比較完了之后,還沒有設進去之前,有一個新的線程進來了,這個數據不就有一些沖突,他就認為這個CAS步驟太多,那么在步驟之間呢,有可能會被其他線程干擾,那么這是不是一個合適的方案呢,上面的這個擔心是多余的,為什么呢,因為CAS整的一個操作過程,它是一個原子操作,是由一條CPU執行完成的,不是由好多CPU去完成的,他只是由一條CPU去完成的,比較交換,他的邏輯是這樣子的

我目標值是不是和我的值是不是相等,相等我就設一個標志,并且把原始值設置到目標里面去,否則我就不設了,CAS它是從指令層面,來保證他這個操作是可靠的,是有效的,那么JAVA當中呢,他提供了一些有關無鎖類的使用,提供比較交換指令,來實現的,與阻塞的相比呢,有鎖的方式呢,因為你有了鎖之后呢,你會導致這個鎖被阻塞,會被掛起,甚至他進入臨界區之前,系統對他進行了掛起,無鎖的方式性能會更好一些,因為一般來說,除非你認為的掛起這個線程,否則在你通過無鎖的方式呢,這個線程是不會被掛起的,會不斷地做重試,根據我們之前有說過,一個線程如果被掛起,如果我只是做一次重試操作,很有可能只是一個循環體,如果我這個循環體不是太復雜的話呢,可能也就是2到3條指令,10條以內的指令,因此在這種情況之下,你相當于在拿一個很少的成本,讀鎖的操作要比阻塞的效果要好很多,在無鎖類當中最有名的一個呢,應該就是AtomicInteger,這個無鎖的整數,這個無鎖的整數呢,他其實就是一個Integer,它是在concurrent的atomic的包當中,他繼承與這個Number,其實Integer也是繼承與Number這個類的public class AtomicInteger extends Number implements java.io.Serializable {他的主要接口有這么多

第一個是get,取得當前這個數字,set就是把這個值設下去,getAndSet,設置一個新的值,返回一個舊的值,CompareAndSet,舊的期望值,我要設置的這個值,如果期望值等于當前值,那我就設置成功,失敗就返回false,getAndIncrement,我取得舊的值,并且把新的值加1,這是一個線程安全的操作,這是減1,這也是一個線程安全的操作,這里是加上一個delta,這兩個函數都可以那他來實現,一個傳1,一個傳-1,那這里是添加和獲取,他返回的是新值,這個和上面也是類似的,先增加再去get,這就是主要的一些接口,那我們先來看一下,接口做了哪些具體的實現,他內部有一個非常重要的字段,一個value,他這個Integer封裝的一個類型,所有的操作都是對value去做的,value才是他內部真正的值,Atomic只是對他的一個包裝而已private volatile int value;看一個比較具有代表性的,/*** Atomically sets the value to the given updated value* if the current value {@code ==} the expected value.** @param expect the expected value* @param update the new value* @return {@code true} if successful. False return indicates that* the actual value was not equal to the expected value.*/ public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }compareAndSet,他做了一個什么事情呢,所期望值是這么多,我要更新的新值是這么多,如果成功了我就返回true,如果失敗就返回false,如果返回false呢,就表示實際的值和期望的值是不相等的,是這么個意思,這里還用到了unsafe,從這個名詞可以看到,它是一個不安全的,為什么JAVA相對于C++和C要比較安全呢,因為他把有關指針,一些內容呢給屏蔽掉了,那么unsafe他恰恰相反,相較于JAVA比較底層呢,它會去提供一些,類似于指針的一些操作,看這個unsafe的compareAndSet,他就是說,我要對于這個類,這個偏移量上的,這個數據,看他的期望值是多少,非常有C感覺的一個操作,那么我們看看valueOffset是什么東西呢private static final long valueOffset;他是一個偏移量,那這個偏移量是哪里來的呢,它是從objectFieldOffset里面拿到的static {try {valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); } }在對象當中的一個偏移量,這里內部肯定是一些C的實現,我們來看一下getAndIncrement/*** Atomically increments by one the current value.** @return the previous value*/ public final int getAndIncrement() {return unsafe.getAndAddInt(this, valueOffset, 1); }這個是指說,我返回一個當前值,并且把這個值加1,我先把這個值get出來,就把這個值加1,因此我加1加出的數據,一定是我當前這個值的加1,不可能是別人改過的數據,然后我會做比較,我比較的期望值是多少呢,是當前值,然后我的目標呢是next,如果有其他線程先于我修改這個數據,那就會導致我這個current,跟實際的期望值不相等,所以我這個設置必然失敗,如果我這個設置失敗,這個函數我返回false,我就走不到return,走不到return怎么辦呢,我就走到for循環重置一次,再把它拿出來,再去嘗試做一個設置,直到我設置成功為止,就在我加和進一步設置之間,沒有人來妨礙我,所以我設置成功,一旦我設置成功呢,我就把舊的那個值給返回,從這個代碼當中呢,所謂的無鎖算法,基本上它會套在一個死循環當中,死循環里面一直做重試,知道成功為止,這個也是一個非常通用的一個思路,其他一些應該也是差不多的,/*** Atomically adds the given value to the current value.** @param delta the value to add* @return the previous value*/ public final int getAndAdd(int delta) {return unsafe.getAndAddInt(this, valueOffset, delta); }基本上都是一個死循環,下面我們來看一下unsafe

Unsafe提供了一些不太安全的一些操作,在JDK內部,并不是對外提供的,如果你要是想去拿unsafe實例呢,實際上是需要動一些手腳的,他提供的一些操作呢,根據偏移量去設置一個數據,什么叫根據偏移量去設置一個數據呢,就是我們剛才在這里有看過,這也是unsafe里面的一個函數static {try {valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); } }我拿到AtomicInteger實例,這個實例他的字段,他在這個類上的偏移量是多少,這個怎么理解呢,如果說我們有寫過C的代碼呢,就會比較清楚,我們C里面有一個結構體,結構體如果你定義兩個,一個int a,第二個是int b,這個時候如果說,a的偏移量是0,int是占4個字節的,b的偏移量就是4,你只要給我結構的基地址,加上偏移量呢,offset就是4,我加上偏移量4之后呢,拿到的就是b所在的地址,我拿到b這個地址之后呢,我就可以對b進行操作,到JAVA當中也是一樣,只不過我是一個class,因為我還有對象頭部的一些信息,然后才可能是我的字段,比如我有個int value,或者我還有其他的亂七八糟的東西,那么object的offset方法呢,要拿的就是類class的一個偏移量,所以我們后面就可以根據這個偏移量呢,去做一些設置,/*** Atomically sets the value to the given updated value* if the current value {@code ==} the expected value.** @param expect the expected value* @param update the new value* @return {@code true} if successful. False return indicates that* the actual value was not equal to the expected value.*/ public final boolean compareAndSet(int expect, int update) {return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }對于這個實例,在C里面有一個首地址,在這個偏移量的位置上,我去做一個int的一個設置,我希望它是expect的,然后他還提供一個park,就是把這個線程停下來,我們在后面的課程當中會講到,然后底層的CAS操作,是在unsafe里面實現的,因為Unsafe這個類呢,是非公開的API,可能會出現比較大的差異,因為他不保證是向前或向后兼容的,因為他并不希望你用這個東西,這里就是unsafe的一些函數,getint什么意思呢,我把這個對象的offset,把它看成一個整數,Unsafe在JDK內部是被大量使用的,而且包括其他第三方的一些框架,高性能的一些框架,內部也會使用Unsafe這個類,我們再來看一下AtomicReference

和AtomicInteger相比,AtomicIngeger他封裝的是一個整數,而Reference他是引用,他是一個對象的引用,你只要對這個對象的引用進行修改,那你就可以用AtomicReference進行修改,我們可以看一下,這個AtomicReferecne他是一個模板/*** An object reference that may be updated atomically. See the {@link* java.util.concurrent.atomic} package specification for description* of the properties of atomic variables.* @since 1.5* @author Doug Lea* @param <V> The type of object referred to by this reference*/ public class AtomicReference<V> implements java.io.Serializable它帶有一個目標的變量V,他可以封裝任意類型的數據,他和Integer非常類似private volatile V value;他也有一個valueOffsetprivate static final long valueOffset;static {try {valueOffset = unsafe.objectFieldOffset(AtomicReference.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); } }他也有ge方法/*** Gets the current value.** @return the current value*/ public final V get() {return value; }/*** Sets to the given value.** @param newValue the new value*/ public final void set(V newValue) {value = newValue; }/*** Atomically sets the value to the given updated value* if the current value {@code ==} the expected value.* @param expect the expected value* @param update the new value* @return {@code true} if successful. False return indicates that* the actual value was not equal to the expected value.*/ public final boolean compareAndSet(V expect, V update) {return unsafe.compareAndSwapObject(this, valueOffset, expect, update); }這些是比較常用的,/*** Atomically sets to the given value and returns the old value.** @param newValue the new value* @return the previous value*/ @SuppressWarnings("unchecked") public final V getAndSet(V newValue) {return (V)unsafe.getAndSetObject(this, valueOffset, newValue); }Integer我們這里使用10個線程,對Integer做add操作,如果說它是線程安全的話呢,每個線程加1萬次,那10個線程就是10萬 package com.learn.thread;import java.util.concurrent.atomic.AtomicInteger;public class AtomicIntegerDemo {static AtomicInteger i = new AtomicInteger();public static class AddThread implements Runnable{@Overridepublic void run() {for(int k=0;k<1000;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);}} 證明他的線程安全性是沒有問題的,下面我們來看一下Reference,這里是使用Reference來封裝一個Stringhttps://www.jianshu.com/p/9de799ef9742我把String指向abc這個字符串,然后嘗試把它修改成def,大家可以看到,這段代碼我開啟的是10個線程,很顯然在這個修改過程當中,只有一個線程是能夠成功的,這里讓每個線程休眠若干秒,都是有一個隨機的線程,而其他線程由于被改成def之后呢,就不可能執行修改這個操作了,這里第10個線程修改這個操作 package com.learn.thread;import java.util.concurrent.atomic.AtomicReference;public class AtomicReferenceTest {public static final AtomicReference<String> atomicStr=new AtomicReference<String>("abc");public static void main(String[] args) {for(int i=0;i<10;i++) {new Thread(){public void run() {try {Thread.sleep((int)Math.abs(Math.random()*100));}catch (InterruptedException e) {e.printStackTrace();}if(atomicStr.compareAndSet("abc", "def")) {System.out.println("Thread:"+Thread.currentThread().getId()+"change value to def");}else {System.out.println("Thread:"+Thread.currentThread().getId()+"failed");}}}.start();}} } Thread:10change value to def Thread:16failed Thread:18failed Thread:17failed Thread:19failed Thread:13failed Thread:14failed Thread:15failed Thread:11failed Thread:12failed 那么其他線程只能宣告失敗,因為compare是不滿足要求的,從這里我們就能夠看到,如果你有一個對象的引用,你希望他可以在多個線程,修改這個引用的時候呢,希望特可以保持線程的安全,你就可以使用AtomicReference,下面再來看一下AtomicStampReference,他也是一個對象的引用,Stample我們一般認為是一個郵戳,時間戳,也就是一個有唯一性標識的一個字段,比如說我們的時間戳,它具有一個唯一性標識,或者我們一個遞增的數據,這樣遞增上去的,沒有重復的,這個東西我們就認為他是一個stample,這個東西是干什么用的呢,他是為了解決這么一個問題,如果我們現在有一個數值,Reference是A,然后我們把它該成了B,然后我們再把它改成了A,這種情況下,有一個線程,他首先拿到Referece是A,接著他通過compare操作呢,接著他拿到A之后呢,他可能會做一些自己額外的一些操作,比如他會做一些計算,然后他開始準備賦值了,這個時候有另外一個線程,把這個A呢改成了B,又有一個線程把這個B又改成了A,然后這個線程計算完畢之后呢,開始做這個操作,他也做這個CS操作,他讀了一下發現還是A,因為他認為A這個數據沒有被人改過,所以他就成功的把這個數據設置回去了,設置成C,那么這么一個過程,如果只是一個簡單的加法,那么問題不大,因為是和過程狀態無關的,只和最終結果是相關的,答案也不會錯,但是有些情況之下呢,我們設置的這個數據,可能是和他的過程相關的,打個比方比如說,我們要給哪個用戶,充10塊錢,那么每個人只能充值一次,不能因為你花了10塊錢,我就再給你充10塊錢,這個不行的,當你對這個數據的變化過程,是敏感的時候,其實用普通的CS操作呢,是沒有辦法來區別這個A和這個A的,如果說我們要區別這個A,那我們有一個很聰明的辦法呢,就是我們給每一個對象加上一個time,加上一個時間戳,假設我們給他加上一個s,改了B之后呢,比如叫s+1,當再次改成A的時候呢,這個time就改成s+2,我不僅僅看A,我還要這個time是否是s,我最開始把A拿出來的時候呢,我不僅僅把A拿出來了,我還要把這個S拿出來了,我既檢查A是否相等,并且還要檢查S是否相等,我發現s不等于s+2的,所以我這個設置就會失敗,StampleReference,他就是解決這個ABA的問題,確保在狀態過程敏感的程序當中呢,不會出現類似的問題

假設這里有一個程序,給用戶充錢,這個money表示用戶的余額,有3個線程不斷地去更新,只要用戶的錢小于20呢,馬上給用戶加20塊錢,這個線程模擬用戶的消費,只要用戶大于10塊錢呢,我就消費10塊錢,這里有三個線程,3個充值線程,和1個消費線程,那么我們可以看到,因為原來是19塊,你充了20塊之后呢,就變成了39,然后用戶消費掉兩個10之后呢,也還會變成19塊,還是變成ABA的問題,我們只希望用戶一次充值的機會,不能給他多次充值,所以和過程狀態是有關系的,就不能滿足你充值的要求了,這里對后臺進程來講,給用戶充值20塊錢在這里,然后他在充值的同時,除了設置金額,還設置了timestamp,就是我們說的Stamp,它會在這上面+1,每一次我修改我都會加1,使得這個stamp永遠都不會重復的,用戶的修改也要對他加1,那么我們來運行這個程序,來看一下結果,這個用戶在整個過程當中只充值一次 package com.learn.thread;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 {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;}}}}}}.start();} } 余額小于20,充值成功,余額:39元 大于10元 成功消費10元,余額:29 大于10元 成功消費10元,余額:19 大于10元 成功消費10元,余額:9 只充值了一次,但是到了19元沒有再次充值的發生,那我們可以想想,如果我們不給他加1,我下面也不給他加1,那這樣的情況呢,就相當于把StampleReference,他不會去判斷stamp有沒有修改過,我們會發現它會不停的運行,只要用戶的錢少了,他就立即沖進去,就是stamp導致的問題,從內部實現上來看呢,他的實現也沒有讓人太驚奇的地方,即使封裝也是在JAVA基礎上做的,/*** Atomically sets the value of both the reference and stamp* to the given update values if the* current reference is {@code ==} to the expected reference* and the current stamp is equal to the expected stamp.** @param expectedReference the expected value of the reference* @param newReference the new value for the reference* @param expectedStamp the expected value of the stamp* @param newStamp the new value for the stamp* @return {@code true} if successful*/ public boolean compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp) {Pair<V> current = pair;returnexpectedReference == current.reference &&expectedStamp == current.stamp &&((newReference == current.reference &&newStamp == current.stamp) ||casPair(current, Pair.of(newReference, newStamp))); }StampedReferece呢,他內部封裝了一個Pairprivate static class Pair<T> {final T reference;final int stamp;private Pair(T reference, int stamp) {this.reference = reference;this.stamp = stamp;}static <T> Pair<T> of(T reference, int stamp) {return new Pair<T>(reference, stamp);} }這一對是什么呢,首先是我們要用的數據reference,對于以前的value來講,這個AtomicReference來講呢,線程把這個value進行一個封裝,這個Value包裝在Pair里面,然后還給他一個stamp,這個stamp就是我們說的,這個stamp就是決定你這個數據能不能設置成功,這個of方法是靜態的工廠方法,他生成一個Pair的實例,StampedReference里面是包裝了Pair實例,當你去拿Reference的時候呢,當然就返回pair的reference,/*** Returns the current value of the reference.** @return the current value of the reference*/ public V getReference() {return pair.reference; }/*** Returns the current value of the stamp.** @return the current value of the stamp*/ public int getStamp() {return pair.stamp; }當你視圖去修改reference的時候呢,你要提供一個期望的引用數據,你要設置你這個對象的引用,以及你的stamp值,你什么情況下能夠設置成功呢,當你期望的這個值和你當前的這個值相等,并且這個stamp值也是一樣的情況下呢,這兩個都相等的情況下,才有可能設置的成功,并且說如果你期望的這個值,新的值和當前的值,也可以認為是成功的一個標志,private boolean casPair(Pair<V> cmp, Pair<V> val) {return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val); }下面我們再來看一下數組,我們有了引用,有了普通的Integer,當然還有一個Long,還有對數組的一個支持,那么數組的支持是什么樣的一個情況呢,數組相對來講,里面就是有若干個成員,整數的數組,這里必然需要一些API,來取得他下標的一個值

相對于Integer相比呢,他要多傳入一個下標,其他都是非常非常類似的,因為這個數組是AtomicIntegerArray,所以它里面的每一項,每一個元素,他都是線程安全的,只要你調用這個方法去做,這里我們開了10個線程,一運行這個程序之后呢,所有的數據都是10萬 package com.learn.thread;import java.util.concurrent.atomic.AtomicIntegerArray;public class AtomicIntegerArrayDemo {static AtomicIntegerArray arr=new AtomicIntegerArray(10);public static class AddThread implements Runnable{@Overridepublic void run() {for(int k=0;k<100000;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);} } [100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000] 其實每個位都是線程安全的,那么像這樣的AtomicIntegerArray,它是怎么實現的呢,它內部會封裝一個普通的array,private final int[] array;他也沒有volatile,也不需要,當你試圖去get的時候呢,它里面有一個方法getRaw/*** Gets the current value at position {@code i}.** @param i the index* @return the current value*/ public final int get(int i) {return getRaw(checkedByteOffset(i)); }private int getRaw(long offset) {return unsafe.getIntVolatile(array, offset); }也就是unsafe當中,去取得一個整數的一個數值,從哪里取呢,從我們這個array對象當中取,取哪個位置呢,取這個offset,取這個偏移量,也就是對這個array來講,對這個數組來講,偏移為offset的整數,他把他拿出來,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; }對于一些高性能的實現當中,并不會使用synchronized,它會用一些相對比較復雜,偏移量的方式,這里有一個checkedByteOffset,去拿第i個下標,他的偏移量是多少,那么這個偏移量是多少呢,從這里這么算出來的,當然你不可以越界,越界就直接拋異常了,他通過byteOffset去算,這里我們詳細解釋一下private static final int base = unsafe.arrayBaseOffset(int[].class);第一個元素的基地址,其他元素的首地址,static {int scale = unsafe.arrayIndexScale(int[].class);if ((scale & (scale - 1)) != 0)throw new Error("data type scale not a power of two");shift = 31 - Integer.numberOfLeadingZeros(scale); }

IntegerFieldUpdater,整數的字段的更新,比如我們在系統中使用成員變量,在我們最開始使用的時候呢,我們只是簡單地把它定義為int,但是你在后面的過程當中呢,我可能不想去改這個數據類型,這個時候就可以選擇IntegerFieldUpdater,普通變量也享受原子操作

這里分數顯然不是Atomic的一個定義,但是是volatile,如果我們沒有把它定義成volatile沒有關系,如果你把int改成AtomicInteger,那這個改動相對而言會比較大,AtomicIntegerFieldUpdater,我們來修改這個int和score,在內部他也是使用反射/*** Creates and returns an updater for objects with the given field.* The Class argument is needed to check that reflective types and* generic types match.** @param tclass the class of the objects holding the field* @param fieldName the name of the field to be updated* @param <U> the type of instances of tclass* @return the updater* @throws IllegalArgumentException if the field is not a* volatile integer type* @throws RuntimeException with a nested reflection-based* exception if the class does not hold field or is the wrong type,* or the field is inaccessible to the caller according to Java language* access control*/ @CallerSensitive public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass,String fieldName) {return new AtomicIntegerFieldUpdaterImpl<U>(tclass, fieldName, Reflection.getCallerClass()); }AtomicIntegerFieldUpdaterImpl(final Class<T> tclass,final String fieldName,final Class<?> caller) {final Field field;final int modifiers;try {field = AccessController.doPrivileged(new PrivilegedExceptionAction<Field>() {public Field run() throws NoSuchFieldException {return tclass.getDeclaredField(fieldName);}});modifiers = field.getModifiers();sun.reflect.misc.ReflectUtil.ensureMemberAccess(caller, tclass, null, modifiers);ClassLoader cl = tclass.getClassLoader();ClassLoader ccl = caller.getClassLoader();if ((ccl != null) && (ccl != cl) &&((cl == null) || !isAncestor(cl, ccl))) {sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);}} catch (PrivilegedActionException pae) {throw new RuntimeException(pae.getException());} catch (Exception ex) {throw new RuntimeException(ex);}if (field.getType() != int.class)throw new IllegalArgumentException("Must be integer type");if (!Modifier.isVolatile(modifiers))throw new IllegalArgumentException("Must be volatile type");// Access to protected field members is restricted to receivers only// of the accessing class, or one of its subclasses, and the// accessing class must in turn be a subclass (or package sibling)// of the protected member's defining class.// If the updater refers to a protected field of a declaring class// outside the current package, the receiver argument will be// narrowed to the type of the accessing class.this.cclass = (Modifier.isProtected(modifiers) &&tclass.isAssignableFrom(caller) &&!isSamePackage(tclass, caller))? caller : tclass;this.tclass = tclass;this.offset = U.objectFieldOffset(field); } package com.learn.thread;import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;public class AtomicIntegerFieldUpdaterDemo {public static class Candidate{int id;volatile int score;}public final static AtomicIntegerFieldUpdater<Candidate> scoreUpdater=AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");//檢查Updater是否工作正確public static AtomicInteger allScore=new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {final Candidate stu=new Candidate();Thread[] ts=new Thread[10000];for(int i=0;i<10000;i++) {ts[i]=new Thread() {public void run() {if(Math.random()>0.4) {scoreUpdater.incrementAndGet(stu);allScore.incrementAndGet();}}};ts[i].start();}for(int i=0;i<10000;i++) {ts[i].join();}System.out.println("score="+stu.score);System.out.println("allscore="+allScore);} } 大家如果對JDK自帶的Vector比較屬性的話呢,Vector是一個向量,是一個數組,我們介紹add方法的一個實現,/*** Adds the specified component to the end of this vector,* increasing its size by one. The capacity of this vector is* increased if its size becomes greater than its capacity.** <p>This method is identical in functionality to the* {@link #add(Object) add(E)}* method (which is part of the {@link List} interface).** @param obj the component to be added*/ public synchronized void addElement(E obj) {modCount++;ensureCapacityHelper(elementCount + 1);elementData[elementCount++] = obj; }他是個同步方法,只有一個線程能夠對數據進行操作,/*** The array buffer into which the components of the vector are* stored. The capacity of the vector is the length of this array buffer,* and is at least large enough to contain all the vector's elements.** <p>Any array elements following the last element in the Vector are null.** @serial*/ protected Object[] elementData;所有的元素實際上都保存在object數組當中,當add一個元素進去的時候呢,ensureCapacityHelper對容量的檢查,數組容量是固定的,所以當你初始化Vector的時候呢,他元素的格式實際上已經確定在里面了,所以每次添加之前,他都得看,我現在會不會越界,如果越界我就要做擴展/*** This implements the unsynchronized semantics of ensureCapacity.* Synchronized methods in this class can internally call this* method for ensuring capacity without incurring the cost of an* extra synchronization.** @see #ensureCapacity(int)*/ private void ensureCapacityHelper(int minCapacity) {// overflow-conscious codeif (minCapacity - elementData.length > 0)grow(minCapacity); }private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length;int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);elementData = Arrays.copyOf(elementData, newCapacity); }擴展的時候我怎么擴呢,capacityIncrement這個你在構造函數中是可以指定他的,不指定他就是0,就返回oldCapacity老的容量,新的容量是誰決定的呢,絕大部分是不指定的,成倍的增長可能并不是特別的合適,下面我們來看一下無鎖的Vector,這里是為無鎖的Vector做一些鋪墊,這里用到的是ReferenceArray,對象的數組,多線程考慮的,并且是無鎖的,并行程序就有這個問題,當你進行修改,你多個線程的同步時非常困難的,多線程的難度就是當你多個線程去修改的時候呢,當我第一個元素存儲滿了,當我第一個數組存儲滿了,如果你再往后追加元素,不夠用的時候呢,我第一個元素,第一個數組,我是不會去動他的,我就往后擴充,當我第二個滿了我也不動他,buckets籃子,他有好多個籃子,第i個籃子能放多少籃子呢,是前面籃子的個數乘以2,無鎖去做會有一個好處,你可以毫無顧忌的不斷地去做這個重試,在程序的各個地方你覺得有需要的地方,你就可以調用compareAndSet,失敗就失敗了,失敗也沒有什么關系

?

總結

以上是生活随笔為你收集整理的无锁-1的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。