摘要:提供這些原子類的目的就是為了解決基本類型操作的非原子性導致在多線程并發情況下引發的問題。測試代碼引發的線程問題最終的值為如果是原子操作,那么結果應該就是,反復運行幾次發現結果大部分情況下都不是,這也證明了的非原子性在多線程下產生的問題。
AtomicInteger的原理
java的并發原子包里面提供了很多可以進行原子操作的類,比如:
AtomicInteger
AtomicBoolean
AtomicLong
AtomicReference
等等,一共分為四類:原子更新基本類型(3個)、原子更新數組、原子更新引用和原子更新屬性(字段)。、提供這些原子類的目的就是為了解決基本類型操作的非原子性導致在多線程并發情況下引發的問題。那么非原子性的操作會引發什么問題呢?下面我們通過一個示例來看一下。
1. i++引發的問題我們知道基本類型的賦值操作是原子操作,但是類似這種i++的操作并不是原子操作,通過反編譯代碼我們可以大致了解此操作分為三個階段:
tp1 = i; //1 tp2 = tp1 + 1; //2 i = tp2; //3
如果有兩個線程m和n要執行i++操作,因為重排序的影響,代碼執行順序可能會發生改變。如果代碼的執行順序是m1 - m2 - m3 - n1 - n2 - n3,那么結果是沒問題的,如果代碼的執行順序是m1 - n1 - m2 - n2 - m3 - n3那么很明顯結果就會出錯。
測試代碼package com.wangjun.thread; public class AtomicIntegerTest { private static int n = 0; public static void main(String[] args) throws InterruptedException { //i++引發的線程問題 Thread t1 = new Thread() { public void run() { for(int i = 0; i < 1000; i++) { n++; } }; }; Thread t2 = new Thread() { public void run() { for(int i = 0; i < 1000; i++) { n++; } }; }; t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("最終n的值為:" + n); } }
如果i++是原子操作,那么結果應該就是2000,反復運行幾次發現結果大部分情況下都不是2000,這也證明了i++的非原子性在多線程下產生的問題。當然我們可以通過加鎖的方式保證操作的原子性,但本文的重點是使用原子類的解決這個問題。
最終n的值為:1367 --- 最終n的值為:1243 --- 最終n的值為:13802. AtomicInteger的原子操作
上面的問題可以使用AtomicInteger來解決,我們更改一下代碼如下:
package com.wangjun.thread; import java.util.concurrent.atomic.AtomicInteger; public class AtomicIntegerTest { private static AtomicInteger n2 = new AtomicInteger(0); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread() { public void run() { for(int i = 0; i < 1000; i++) { n2.incrementAndGet(); } }; }; Thread t2 = new Thread() { public void run() { for(int i = 0; i< 1000; i++) { n2.incrementAndGet(); } } }; t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("最終n2的值為:" + n2.toString()); } }
多次運行,發現結果永遠是2000,由此可以證明AtomicInteger的操作是原子性的。
最終n2的值為:2000
那么AtomicInteger是通過什么機制來保證原子性的呢?接下來,我們對源碼進行一下分析。
3. AtomicInteger源碼分析 構造函數private volatile int value; /* * AtomicInteger內部聲明了一個volatile修飾的變量value用來保存實際值 * 使用帶參的構造函數會將入參賦值給value,無參構造器value默認值為0 */ public AtomicInteger(int initialValue) { value = initialValue; }自增函數
import sun.misc.Unsafe; private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } /* * 可以看到自增函數中調用了Unsafe函數的getAndAddInt方法 */ public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; }
那么這個getAndAddInt方法是干嘛的呢,首先來了解一下Unsafe這個類。
Unsafe類是在sun.misc包下,不屬于Java標準。但是很多Java的基礎類庫,包括一些被廣泛使用的高性能開發庫都是基于Unsafe類開發的,比如Netty、Cassandra、Hadoop、Kafka等。Unsafe類在提升Java運行效率,增強Java語言底層操作能力方面起了很大的作用。 Unsafe類使Java擁有了像C語言的指針一樣操作內存空間的能力,同時也帶來了指針的問題。過度的使用Unsafe類會使得出錯的幾率變大,因此Java官方并不建議使用的,官方文檔也幾乎沒有。 通常我們最好也不要使用Unsafe類,除非有明確的目的,并且也要對它有深入的了解才行。
再來說Unsafe的getAndAddInt,通過反編譯可以看到實現代碼:
/* * 其中getIntVolatile和compareAndSwapInt都是native方法 * getIntVolatile是獲取當前的期望值 * compareAndSwapInt就是我們平時說的CAS(compare and swap),通過比較如果內存區的值沒有改變,那么就用新值直接給該內存區賦值 */ public final int getAndAddInt(Object paramObject, long paramLong, int paramInt) { int i; do { i = getIntVolatile(paramObject, paramLong); } while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt)); return i; }
incrementAndGet是將自增后的值返回,還有一個方法getAndIncrement是將自增前的值返回,分別對應++i和i++操作。同樣的decrementAndGet和getAndDecrement則對--i和i--操作。
參考:java的Unsafe類:https://www.cnblogs.com/pkufo...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69561.html
摘要:即使是在多個線程一起執行的時候,一個操作一旦開始,就不會被其他線程干擾。另外是一個變量,在內存中可見,因此可以保證任何時刻任何線程總能拿到該變量的最新值。 個人覺得這一節掌握基本的使用即可! 本節思維導圖: showImg(https://segmentfault.com/img/remote/1460000016855442?w=1244&h=657); 1 Atomic 原子類介紹...
摘要:加鎖才能保證線程安全使用之后,不加鎖,也是線程安全的。確保不出現線程安全問題。一般在數據庫中使用樂觀鎖都會拿版本號作為對比值,因為版本號會一直增加,沒有重復的,所以不會出現這個問題。 悲觀鎖: 認為每次獲取數據的時候數據一定會被人修改,所以它在獲取數據的時候會把操作的數據給鎖住,這樣一來就只有它自己能夠操作,其他人都堵塞在那里。 樂觀鎖: 認為每次獲取數據的時候數據不會被別人修改,所以...
摘要:今天給大家總結一下,面試中出鏡率很高的幾個多線程面試題,希望對大家學習和面試都能有所幫助。指令重排在單線程環境下不會出先問題,但是在多線程環境下會導致一個線程獲得還沒有初始化的實例。使用可以禁止的指令重排,保證在多線程環境下也能正常運行。 下面最近發的一些并發編程的文章匯總,通過閱讀這些文章大家再看大廠面試中的并發編程問題就沒有那么頭疼了。今天給大家總結一下,面試中出鏡率很高的幾個多線...
摘要:原子類的作用多線程操作,性能開銷太大并不是原子操作。每次比較的是兩個對象性能比要好使用時,在高并發下大量線程會同時去競爭更新同一個原子變量,但是由于同時只有一個線程的會成功,所以其他線程會不斷嘗試自旋嘗試操作,這會浪費不少的資源。 AtomicInteger 原子類的作用 多線程操作,Synchronized 性能開銷太大count++并不是原子操作。因為count++需要經過讀取-...
閱讀 2371·2021-11-24 10:31
閱讀 3426·2021-11-23 09:51
閱讀 2239·2021-11-15 18:11
閱讀 2386·2021-09-02 15:15
閱讀 2452·2019-08-29 17:02
閱讀 2284·2019-08-29 15:04
閱讀 830·2019-08-29 12:27
閱讀 2853·2019-08-28 18:15