并發(fā)與高并發(fā)系列之線程安全性之原子性
本文轉(zhuǎn)載自微信公眾號(hào)「安琪拉的博客」,作者安琪拉。轉(zhuǎn)載本文請(qǐng)聯(lián)系安琪拉的博客公眾號(hào)。
大家好,我是安琪拉,這是并發(fā)編程的第五集,完整大綱如下:
面試官:你好,你先自我介紹一下吧。
安琪拉:面試官你好,我是草叢三婊,最強(qiáng)中單,火球擁有者、不焚者,安琪拉,這是我的簡(jiǎn)歷,請(qǐng)過目。
面試官:聽前一個(gè)面試官說你Java并發(fā)這塊掌握的不錯(cuò),我們深入的交流一下;
安琪拉:好好好,可以交流的深入一點(diǎn)
面試官:什么是線程安全性?
安琪拉:這個(gè)問題第一次被問,但是個(gè)好問題。
當(dāng)多個(gè)線程訪問某個(gè)類時(shí),不管運(yùn)行環(huán)境采用何種調(diào)度方式或者這些進(jìn)程將如何交替執(zhí)行,并且在主調(diào)代碼中不需要任何額外的協(xié)同或者同步,這個(gè)類都能表現(xiàn)出正確的行為,那么這個(gè)類是線程安全的。
面試官:線程安全性有哪三大特點(diǎn)? 或者說線程不安全是由于什么引起的?
安琪拉:【太老套了吧,能不能來點(diǎn)新的】
線程不安全的原因:
當(dāng)前的一個(gè)操作可能不是原子的,執(zhí)行過程中會(huì)被打斷,其他線程有能力修改共享變量的值,同時(shí)存在線程修改的值不是立即對(duì)其他線程可見的,因?yàn)榫€程有自己的執(zhí)行空間,另外一點(diǎn)就是存在程序可能存在亂序執(zhí)行的情況,單線程沒問題,但是多個(gè)線程同時(shí)執(zhí)行,線程共享的數(shù)據(jù)會(huì)出現(xiàn)錯(cuò)亂,以上說的自己?jiǎn)栴}歸納出線程安全需要保證的三個(gè)特性:
- 原子性
提供互斥訪問、同一時(shí)刻只能有一個(gè)線程在操作
- 可見性
一個(gè)線程對(duì)主內(nèi)存的修改可以及時(shí)地被其他線程看到
- 有序性
有序性是指程序在執(zhí)行的時(shí)候,程序的代碼執(zhí)行順序和語句的順序是一致的。(你可能會(huì)想難道還有不一致的,是的,因?yàn)榇嬖谥噶钪嘏判?,為什么?huì)有指令重排,因?yàn)樾阅軆?yōu)化的需要,比如把多次訪問主存合并到一起執(zhí)行比計(jì)算和訪問主存交替訪問更高效),重排序過程不會(huì)影響到單線程程序的執(zhí)行,卻會(huì)影響到多線程并發(fā)執(zhí)行的正確性。
面試官:那你用過java.util.concurrent.atomic包下原子性相關(guān)的類嗎?
安琪拉:用過的,Java提供了很多AtomicXXX相關(guān)的原子類,如下圖所示:
面試官:能舉個(gè)例子說明下用法嗎?
安琪拉:比如存在并發(fā),計(jì)數(shù)的場(chǎng)景,以netty為例,它的線程池工廠類如下:
nextId就是 AtomicInteger類型的。每次創(chuàng)建線程給線程命名的時(shí)候, 代碼如下:
- public Thread newThread(Runnable r) {
- Thread t = this.newThread(new DefaultThreadFactory.DefaultRunnableDecorator(r), this.prefix + this.nextId.incrementAndGet());
- try {
- if (t.isDaemon()) {
- if (!this.daemon) {
- t.setDaemon(false);
- }
- } else if (this.daemon) {
- t.setDaemon(true);
- }
- if (t.getPriority() != this.priority) {
- t.setPriority(this.priority);
- }
- } catch (Exception var4) {
- }
- return t;
- }
通過 incrementAndGet 實(shí)現(xiàn)原子性的 +1。
面試官:如果不用AtomicInteger,就用普通的int 會(huì)有什么后果?
安琪拉:首先我們知道 +1 操作不是原子性的,可以分成這么幾條指令:取數(shù)指令,將數(shù)據(jù)壓入操作數(shù)棧,執(zhí)行+1操作,賦值。
關(guān)于指令這塊,扔個(gè)藍(lán)。我們編譯一段Java code 看一下。
代碼和字節(jié)碼指令分別為:
- public static int add(int a,int b){
- int c = 0;
- c = a + b;
- return c;
- }
指令,對(duì)應(yīng)的操作解釋也有,如下:
- public static int add(int, int);
- Code:
- 0: iconst_0 //初始化常量0壓入操作數(shù)棧頂
- 1: istore_2 //彈出操作數(shù)棧棧頂元素,保存到局部變量表第2個(gè)位置
- 2: iload_0 //復(fù)制a變量的值入棧
- 3: iload_1 //復(fù)制b變量的值入棧
- 4: iadd //執(zhí)行加操作,相加結(jié)果放在棧頂
- 5: istore_2 //彈出操作數(shù)棧棧頂元素,保存到局部變量表第2個(gè)位置
- 6: iload_2 //復(fù)制局部變量表第2個(gè)位置的值入棧
- 7: ireturn //彈棧,返回結(jié)果
寫這么多就是為了讓大家明白 a += 1 這種操作它不是原子的,是有多條指令組成,真的不容易,快給我點(diǎn)個(gè)贊,好心人的藍(lán)buff
面試官:那能跟我講下Atomic 的實(shí)現(xiàn)原理嗎?
安琪拉:【要開始卷了,到安琪拉最愛的源碼環(huán)節(jié)】
- /**
- * Atomically increments by one the current value.
- *
- * @return the updated value
- */
- public final int incrementAndGet() {
- return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
- }
代碼很短,注釋就一句話,原子性的增加當(dāng)前值。
繼續(xù)下探:
- public final int getAndAddInt(Object var1, long var2, int var4) {
- int var5;
- do {
- var5 = this.getIntVolatile(var1, var2);
- } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
- return var5;
- }
入?yún)⑹侨齻€(gè)值:var1、var2、var4 ,我們先看下這三個(gè)值分別是什么?
Val1:this ,也就是AtomicInteger 對(duì)象nextId
Val2:valueOffset 看下代碼,我另外畫了個(gè)圖,我們知道一個(gè)對(duì)象存儲(chǔ)空間由對(duì)象頭和成員變量組成的,那valueOffset 就是成員變量value 在AtomicInteger 對(duì)象中的偏移量。
初學(xué)者可能會(huì)問,函數(shù)放在哪呢?函數(shù)都放在方法區(qū),因?yàn)槭菍儆陬惖模皇菍?duì)象私有的。
- 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); }
- }
- private volatile int value;
Val4:1
那開始詳細(xì)解釋下,下面這段代碼:
compareAndSwapInt 方法:比較val1(AtomicInteger對(duì)象)的var2(valueOffset偏移量)的值與var5(原始值)是否相等,如果相等,讓值更新成var5(原始值) + val4(1)
- //val1: nextId val2: valueOffset val4: 1
- public final int getAndAddInt(Object var1, long var2, int var4) {
- int var5; //臨時(shí)變量
- do {
- var5 = this.getIntVolatile(var1, var2); //這是個(gè)native方法,獲取value的值
- //比較val1(AtomicInteger對(duì)象)的var2(valueOffset偏移量)的值與var5(原始值)是否相等,如果相等,讓值更新成var5(原始值) + val4(1)
- } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
- return var5;
- }
- public native int getIntVolatile(Object var1, long var2);
compareAndSwapInt 就是Java中非常重要,也是非常出名的CAS操作,比較并交換,并發(fā)底層框架用到的地方很多。
compareAndSwapInt 會(huì)返回CAS支持狀態(tài),如果執(zhí)行失敗,會(huì)循環(huán)執(zhí)行,直到成功。
失敗的原因一般是同時(shí)有別的線程修改了這個(gè)變量的值,所以比較的時(shí)候不相等,下次執(zhí)行會(huì)獲取最新值執(zhí)行CAS。
。。。。嚶嚶嚶,打字好累啊,先寫到這,要去吃自助餐了,明天再寫可見性和有序性。