摘要:我們使用命令查看字節碼會發現在虛擬機中這個自增運算使用了條指令。其實這么說也不是最嚴謹的,因為即使經過編譯后的字節碼只使用了一條指令進行運算也不代表這條指令就是原子操作。
volatile的語義:
1、保證被volatile修飾的變量對所有其他的線程的可見性。
2、使用volatile修飾的變量禁止指令重排優化。
看代碼:
public class InheritThreadClass extends Thread{ private static volatile int a = 0; @Override public void run() { for(int i = 0; i < 1000; i++){ a++; } } public static void main(String[] args) { InheritThreadClass[] threads = new InheritThreadClass[100]; for(int i=0; i < 100; i++){ threads[i] = new InheritThreadClass(); threads[i].start(); } //等待所有子線程結束 while(Thread.activeCount() > 1){ Thread.yield(); } //這段代碼會在所有子線程運行完畢之后執行 System.out.println(a); //(1) } }
上面的代碼中創建了100個線程,然后在每個線程中對變量a進行了1000次的自增運算,那么也就意味著,如果這段代碼可以正確的并發運行,最后在代碼(1)處應該輸出100000。但是多次運行你會發現每次輸出的結果并不是我們預期的那樣,而都是小于等于100000。也就是說每次運行的結果是不固定的不一樣的,這是為什么呢? 因為通過上面volatile關鍵字的語義我們知道被該關鍵字修飾的變量對所有的線程是可見的啊,那怎么會出現這種情況呢?難道語義有錯? 那是不可能的,語義肯定是沒有錯的。
我們知道每一個線程都有自己的私有內存,而線程之間的通信是通過主存來實現的,volatile在這里保證多線程的可見性的意思是說:如果一個線程修改了被volatile關鍵字修飾的變量,會立馬刷新到主內存中,其他需要使用這個變量的線程不在從自己的私有內存中獲取了,而是直接從主內存中獲取。雖然volatile關鍵字保證了變量對所有線程的可見性,但是java代碼中的運算操作并非原子操作。
我們使用javap命令查看字節碼(javap -verbose InheritThreadClass.class)會發現在虛擬機中這個自增運算使用了4條指令(getstatic, iconst_1, iadd, putstatic)。 當getstatic指令把a的值壓入棧頂時,volatile關鍵字保證了a的值此時是正確的,但是在執行iconst_1、iadd這些指令時其他線程有可能已經把a的值加大了,而已經在操作棧頂的值就變成了過期的數據了,所以putstatic指令執行后可能又把較小的a值同步回主內存了。 所以它不是一個原子運算,因此在多線程的情況下它并不是一個安全的操作。其實這么說也不是最嚴謹的,因為即使經過編譯后的字節碼只使用了一條指令進行運算也不代表這條指令就是原子操作。因為一條字節碼指令在解釋執行時,解釋器需要運行許多行代碼才能實現該條指令的語義,而即使是編譯執行,一條字節碼指令也可能需要轉化成多條本地機器碼指令。
所以有關volatile的變量對其他線程的”可見性“的語義描述并不能得出這樣的結論:基于volatile變量的運算在高并發下是安全的。
那這種在高并發下的自增運算如何做到線程安全呢?可以使用synchronized,但是加鎖的話性能開銷太大,高并發下不是一個明智之選。可以使用并發包java.util.concurrent.atomic下的AtomicInteger原子類。
看代碼:
private static volatile AtomicInteger a = new AtomicInteger(0); @Override public void run() { for(int i = 0; i < 1000; i++){ a.getAndIncrement(); } }
上面的代碼就可以在高并發下正確的運行,每次輸出都是100000。
看AtomicInteger源碼:
**//部分關鍵字段** private static final Unsafe unsafe = Unsafe.getUnsafe(); /* valueOffset這個是指類中相應字段在該類的偏移量, 在下面的靜態塊中調用objectFieldOffset()方法初始化。 */ private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; //objectFieldOffset方法是一個本地方法 public native long objectFieldOffset(Field field); // AtomicInteger的構造器之一 public AtomicInteger(int initialValue) { value = initialValue; } //getAndIncrement()這個方法的源碼實現 public final int getAndIncrement() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } } //get()方法的實現 public final int get() { return value; } /*compareAndSet(int expect, int update)方法內部又直接調用了 *unsafe的compareAndSwapInt方法,這里直接上compareAndSwapInt源碼的實現 *在obj的offset位置比較內存中的值和期望的值,如果相同則更新。 *這是一個本地方法,應該是原子的,因此提供了一種不可中斷的方式更新 */ public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66721.html
摘要:所以接下來,我們需要簡單的介紹下多線程中的并發通信模型。比如中,以及各種鎖機制,均為了解決線程間公共狀態的串行訪問問題。 并發的學習門檻較高,相較單純的羅列并發編程 API 的枯燥被動學習方式,本系列文章試圖用一個簡單的栗子,一步步結合并發編程的相關知識分析舊有實現的不足,再實現邏輯進行分析改進,試圖展示例子背后的并發工具與實現原理。 本文是本系列的第一篇文章,提出了一個簡單的業務場景...
摘要:對象頭的另外一部分是類型指針,即對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。并不是所有的虛擬機實現都必須在對象數據上保留類型指針,換句話說,查找對象的元數據信息并不一定要經過對象本身,這點將在節討論。 目錄介紹 1.關于int和Integer的問題區別分析 2.Integer的值緩存的原理 2.1 Java 5 中引入緩存特性 2.2 Intege...
摘要:今天給大家總結一下,面試中出鏡率很高的幾個多線程面試題,希望對大家學習和面試都能有所幫助。指令重排在單線程環境下不會出先問題,但是在多線程環境下會導致一個線程獲得還沒有初始化的實例。使用可以禁止的指令重排,保證在多線程環境下也能正常運行。 下面最近發的一些并發編程的文章匯總,通過閱讀這些文章大家再看大廠面試中的并發編程問題就沒有那么頭疼了。今天給大家總結一下,面試中出鏡率很高的幾個多線...
摘要:目的是解決由于多線程通過共享內存進行通信時,存在的原子性可見性緩存一致性以及有序性問題。最多只有一個線程能持有鎖。線程加入規則對象的結束先行發生于方法返回。 前言 學習情況記錄 時間:week 1 SMART子目標 :Java 多線程 學習Java多線程,要了解多線程可能出現的并發現象,了解Java內存模型的知識是必不可少的。 對學習到的重要知識點進行的記錄。 注:這里提到的是Ja...
摘要:該類將整數值與引用關聯起來,可用于原子的更數據和數據的版本號。 CAS的全稱為Compare And Swap,直譯就是比較交換。是一條CPU的原子指令,其作用是讓CPU先進行比較兩個值是否相等,然后原子地更新某個位置的值,其實現方式是基于硬件平臺的匯編指令,在intel的CPU中,使用的是cmpxchg指令,就是說CAS是靠硬件實現的,從而在硬件層面提升效率。 CSA 原理 利用CP...
閱讀 2064·2023-04-25 22:58
閱讀 1408·2021-09-22 15:20
閱讀 2694·2019-08-30 15:56
閱讀 1986·2019-08-30 15:54
閱讀 2101·2019-08-29 12:31
閱讀 2728·2019-08-26 13:37
閱讀 592·2019-08-26 13:25
閱讀 2098·2019-08-26 11:58