成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

i++ 是線程安全的嗎?

RyanQ / 2436人閱讀

摘要:例子先來(lái)看下面的示例來(lái)驗(yàn)證下到底是不是線程安全的。上面的例子我們期望的結(jié)果應(yīng)該是,但運(yùn)行遍,你會(huì)發(fā)現(xiàn)總是不為,至少你現(xiàn)在知道了操作它不是線程安全的了。它的性能比較好也是因?yàn)楸苊饬耸咕€程進(jìn)入內(nèi)核態(tài)的阻塞狀態(tài)。

例子

先來(lái)看下面的示例來(lái)驗(yàn)證下 i++ 到底是不是線程安全的。

1000個(gè)線程,每個(gè)線程對(duì)共享變量 count 進(jìn)行 1000 次 ++ 操作。

上面的例子我們期望的結(jié)果應(yīng)該是 1000000,但運(yùn)行 N 遍,你會(huì)發(fā)現(xiàn)總是不為 1000000,至少你現(xiàn)在知道了 i++
操作它不是線程安全的了。

JMM 模型中對(duì)共享變量的讀寫(xiě)原理。

每個(gè)線程都有自己的工作內(nèi)存,每個(gè)線程需要對(duì)共享變量操作時(shí)必須先把共享變量從主內(nèi)存 load 到自己的工作內(nèi)存,等完成對(duì)共享變量的操作時(shí)再 save 到主內(nèi)存。

問(wèn)題就出在這了,如果一個(gè)線程運(yùn)算完后還沒(méi)刷到主內(nèi)存,此時(shí)這個(gè)共享變量的值被另外一個(gè)線程從主內(nèi)存讀取到了,這個(gè)時(shí)候讀取的數(shù)據(jù)就是臟數(shù)據(jù)了,它會(huì)覆蓋其他線程計(jì)算完的值。。。

這也是經(jīng)典的內(nèi)存不可見(jiàn)問(wèn)題,那么把 count 加上 volatile 讓內(nèi)存可見(jiàn)是否能解決這個(gè)問(wèn)題呢? 答案是:不能。因?yàn)?br>volatile 只能保證可見(jiàn)性,不能保證原子性。多個(gè)線程同時(shí)讀取這個(gè)共享變量的值,就算保證其他線程修改的可見(jiàn)性,也不能保證線程之間讀取到同樣的值然后相互覆蓋對(duì)方的值的情況。

關(guān)于多線程的幾種關(guān)鍵概念請(qǐng)翻閱《多線程之原子性、可見(jiàn)性、有序性詳解》這篇文章。

解決方案

說(shuō)了這么多,對(duì)于 i++ 這種線程不安全問(wèn)題有沒(méi)有其他解決方案呢?當(dāng)然有,請(qǐng)參考以下幾種解決方案。

1、對(duì) i++ 操作的方法加同步鎖,同時(shí)只能有一個(gè)線程執(zhí)行 i++ 操作;

2、使用支持原子性操作的類,如 java.util.concurrent.atomic.AtomicInteger,它使用的是
CAS 算法,效率優(yōu)于第 1 種;

拓展 CAS算法
CAS:Compare and Swap, 翻譯成比較并交換。鏈接描述 
java.util.concurrent包中借助CAS實(shí)現(xiàn)了區(qū)別于synchronouse同步鎖的一種樂(lè)觀鎖,使用這些類在多核CPU的機(jī)器上會(huì)有比較好的性能.
CAS有3個(gè)操作數(shù),內(nèi)存值V,舊的預(yù)期值A(chǔ),要修改的新值B。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí),將內(nèi)存值V修改為B,否則什么都不做。

今天我們主要是針對(duì)AtomicInteger的incrementAndGet做深入分析。

/** 
     * Atomically increments by one the current value. 
     * 
     * @return the updated value 
     */  
    public final int incrementAndGet() {  
        for (;;) {  
            int current = get();  
            int next = current + 1;  
            if (compareAndSet(current, next))  
                return next;  
        }  
    }  

循環(huán)的內(nèi)容是:
1.取得當(dāng)前值
2.計(jì)算+1后的值
3.如果當(dāng)前值沒(méi)有被覆蓋的話設(shè)置那個(gè)+1后的值
4.如果設(shè)置沒(méi)成功, 再?gòu)?開(kāi)始
在這個(gè)方法中可以看到compareAndSet這個(gè)方法,我們進(jìn)入看一下。

/** 
     * 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 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);  
    }  

調(diào)用UnSafe這個(gè)類的compareAndSwapInt

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);  

JAVA程序也就跟蹤到這里為止了,剩下的就是通過(guò)JNI調(diào)用C程序了,可是我奇怪的是為什么變量名都是var1,var2這樣的命名呢?JAVA編程規(guī)范不是說(shuō)不使用1,2等沒(méi)有含義的字符命名嗎?

JNI原生實(shí)現(xiàn)部分

在openJDK中找到找到unsafe.cpp這個(gè)文件,代碼如下:

UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))  
  UnsafeWrapper("Unsafe_CompareAndSwapInt");  
  oop p = JNIHandles::resolve(obj);  
  jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);  
  return (jint)(Atomic::cmpxchg(x, addr, e)) == e;  
UNSAFE_END  

核心方法是compxchg,這個(gè)方法所屬的類文件是在OS_CPU目錄下面,由此可以看出這個(gè)類是和CPU操作有關(guān),進(jìn)入代碼如下:

inline jint     Atomic::cmpxchg    (jint     exchange_value, volatile jint*     dest, jint     compare_value) {  
  // alternative for InterlockedCompareExchange  
  int mp = os::is_MP();  
  __asm {  
    mov edx, dest  
    mov ecx, exchange_value  
    mov eax, compare_value  
    LOCK_IF_MP(mp)  
    cmpxchg dword ptr [edx], ecx  
  }  
}  

這個(gè)方法里面都是匯編指命,看到LOCK_IF_MP也有鎖指令實(shí)現(xiàn)的原子操作,其實(shí)CAS也算是有鎖操作,只不過(guò)是由CPU來(lái)觸發(fā),比synchronized性能好的多。

使用cas的類

ReenTrantLock、countDownLatch、AtomicInteger

ReenTrantLock和synchronized比較

可重入性:
從名字上理解,ReenTrantLock的字面意思就是再進(jìn)入的鎖,其實(shí)synchronized關(guān)鍵字所使用的鎖也是可重入的,兩者關(guān)于這個(gè)的區(qū)別不大。兩者都是同一個(gè)線程沒(méi)進(jìn)入一次,鎖的計(jì)數(shù)器都自增1,所以要等到鎖的計(jì)數(shù)器下降為0時(shí)才能釋放鎖。

鎖的實(shí)現(xiàn):
Synchronized是依賴于JVM實(shí)現(xiàn)的,而ReenTrantLock是JDK實(shí)現(xiàn)的,有什么區(qū)別,說(shuō)白了就類似于操作系統(tǒng)來(lái)控制實(shí)現(xiàn)和用戶自己敲代碼實(shí)現(xiàn)的區(qū)別。前者的實(shí)現(xiàn)是比較難見(jiàn)到的,后者有直接的源碼可供閱讀。

性能的區(qū)別:
在Synchronized優(yōu)化以前,synchronized的性能是比ReenTrantLock差很多的,但是自從Synchronized引入了偏向鎖,輕量級(jí)鎖(自旋鎖)后,兩者的性能就差不多了,在兩種方法都可用的情況下,官方甚至建議使用synchronized,其實(shí)synchronized的優(yōu)化我感覺(jué)就借鑒了ReenTrantLock中的CAS技術(shù)。都是試圖在用戶態(tài)就把加鎖問(wèn)題解決,避免進(jìn)入內(nèi)核態(tài)的線程阻塞。

功能區(qū)別:

便利性:很明顯Synchronized的使用比較方便簡(jiǎn)潔,并且由編譯器去保證鎖的加鎖和釋放,而ReenTrantLock需要手工聲明來(lái)加鎖和釋放鎖,為了避免忘記手工釋放鎖造成死鎖,所以最好在finally中聲明釋放鎖。

鎖的細(xì)粒度和靈活度:很明顯ReenTrantLock優(yōu)于Synchronized

ReenTrantLock獨(dú)有的能力:
1.ReenTrantLock可以指定是公平鎖還是非公平鎖。而synchronized只能是非公平鎖。所謂的公平鎖就是先等待的線程先獲得鎖。
2.ReenTrantLock提供了一個(gè)Condition(條件)類,用來(lái)實(shí)現(xiàn)分組喚醒需要喚醒的線程們,而不是像synchronized要么隨機(jī)喚醒一個(gè)線程要么喚醒全部線程。
3.ReenTrantLock提供了一種能夠中斷等待鎖的線程的機(jī)制,通過(guò)lock.lockInterruptibly()來(lái)實(shí)現(xiàn)這個(gè)機(jī)制。

ReenTrantLock實(shí)現(xiàn)的原理:
在網(wǎng)上看到相關(guān)的源碼分析,本來(lái)這塊應(yīng)該是本文的核心,但是感覺(jué)比較復(fù)雜就不一一詳解了,簡(jiǎn)單來(lái)說(shuō),ReenTrantLock的實(shí)現(xiàn)是一種自旋鎖,通過(guò)循環(huán)調(diào)用CAS(無(wú)鎖算法)操作來(lái)實(shí)現(xiàn)加鎖。它的性能比較好也是因?yàn)楸苊饬耸咕€程進(jìn)入內(nèi)核態(tài)的阻塞狀態(tài)。想盡辦法避免線程進(jìn)入內(nèi)核的阻塞狀態(tài)是我們?nèi)シ治龊屠斫怄i設(shè)計(jì)的關(guān)鍵鑰匙。

CAS和synchronized適用場(chǎng)景

1、對(duì)于資源競(jìng)爭(zhēng)較少的情況,使用synchronized同步鎖進(jìn)行線程阻塞和喚醒切換以及用戶態(tài)內(nèi)核態(tài)間的切換操作額外浪費(fèi)消耗cpu資源;而CAS基于硬件實(shí)現(xiàn),不需要進(jìn)入內(nèi)核,不需要切換線程,操作自旋幾率較少,因此可以獲得更高的性能。

2、對(duì)于資源競(jìng)爭(zhēng)嚴(yán)重的情況,CAS自旋的概率會(huì)比較大,從而浪費(fèi)更多的CPU資源,效率低于synchronized。以java.util.concurrent.atomic包中AtomicInteger類為例,其getAndIncrement()方法實(shí)現(xiàn)如下:

如果compareAndSet(current, next)方法成功執(zhí)行,則直接返回;如果線程競(jìng)爭(zhēng)激烈,導(dǎo)致compareAndSet(current, next)方法一直不能成功執(zhí)行,則會(huì)一直循環(huán)等待,直到耗盡cpu分配給該線程的時(shí)間片,從而大幅降低效率。

java共享鎖實(shí)現(xiàn)原理及CountDownLatch解析 鏈接描述 CountDownLatch使用解說(shuō)

例子1: CountDownLatch是java5中新增的一個(gè)并發(fā)工具類,其使用非常簡(jiǎn)單,下面通過(guò)偽代碼簡(jiǎn)單看一下使用方式:

這是一個(gè)使用CountDownLatch非常簡(jiǎn)單的例子,創(chuàng)建的時(shí)候,需要指定一個(gè)初始狀態(tài)值,本例為2,主線程調(diào)用 latch.await時(shí),除非latch狀態(tài)值為0,否則會(huì)一直阻塞休眠。當(dāng)所有任務(wù)執(zhí)行完后,主線程喚醒,最終執(zhí)行打印動(dòng)作。

例子2:以上只是一個(gè)最簡(jiǎn)單的例子,接著咱們?cè)賮?lái)看一個(gè),這回,咱們想要在任務(wù)執(zhí)行完后做更多的事情,如下圖所示:

這一次,在線程3和線程4中,分別調(diào)用了latch.await(),當(dāng)latch狀態(tài)值為0時(shí),這兩個(gè)線程將會(huì)繼續(xù)執(zhí)行任務(wù),但是順序性是無(wú)法保證的。

CountDownLatch的方便之處在于,你可以在一個(gè)線程中使用,也可以在多個(gè)線程上使用,一切只依據(jù)狀態(tài)值,這樣便不會(huì)受限于任何的場(chǎng)景。

java共享鎖模型

在java5提供的并發(fā)包下,有一個(gè)AbstractQueuedSynchronizer抽象類,也叫AQS,此類根據(jù)大部分并發(fā)共性作了一些抽象,便于開(kāi)發(fā)者實(shí)現(xiàn)如排他鎖,共享鎖,條件等待等更高級(jí)的業(yè)務(wù)功能。它通過(guò)使用CAS和隊(duì)列模型,出色的完成了抽象任務(wù),在此向Doug Lea致敬。

AQS比較抽象,并且是優(yōu)化精簡(jiǎn)的代碼,如果一頭扎進(jìn)去,可能會(huì)比較容易迷失。本篇只解說(shuō)CountDownLatch中使用到的共享鎖模型。

我們以CountDownLatch第二個(gè)例子作為案例來(lái)分析一下,一開(kāi)始,我們創(chuàng)建了一個(gè)CountDownLatch實(shí)例,

此時(shí),AQS中,狀態(tài)值state=2,對(duì)于 CountDownLatch 來(lái)說(shuō),state=2表示所有調(diào)用await方法的線程都應(yīng)該阻塞,等到同一個(gè)latch被調(diào)用兩次countDown后才能喚醒沉睡的線程。接著線程3和線程4執(zhí)行了 await方法,這會(huì)的狀態(tài)圖如下:

注意,上面的通知狀態(tài)是節(jié)點(diǎn)的屬性,表示該節(jié)點(diǎn)出隊(duì)后,必須喚醒其后續(xù)的節(jié)點(diǎn)線程。當(dāng)線程1和線程2分別執(zhí)行完latch.countDown方法后,會(huì)把state值置為0,此時(shí),通過(guò)CAS成功置為0的那個(gè)線程將會(huì)同時(shí)承擔(dān)起喚醒隊(duì)列中第一個(gè)節(jié)點(diǎn)線程的任務(wù),從上圖可以看出,第一個(gè)節(jié)點(diǎn)即為線程3,當(dāng)線程3恢復(fù)執(zhí)行之后,其發(fā)現(xiàn)狀態(tài)值為通知狀態(tài),所以會(huì)喚醒后續(xù)節(jié)點(diǎn),即線程4節(jié)點(diǎn),然后線程3繼續(xù)做自己的事情,到這里,線程3和線程4都已經(jīng)被喚醒,CountDownLatch功成身退。

上面的流程,如果落實(shí)到代碼,把 state置為0的那個(gè)線程,會(huì)判斷head指向節(jié)點(diǎn)的狀態(tài),如果為通知狀態(tài),則喚醒后續(xù)節(jié)點(diǎn),即線程3節(jié)點(diǎn),然后head指向線程3節(jié)點(diǎn),head指向的舊節(jié)點(diǎn)會(huì)被刪除掉。當(dāng)線程3恢復(fù)執(zhí)行后,發(fā)現(xiàn)自身為通知狀態(tài),又會(huì)把head指向線程4節(jié)點(diǎn),然后刪除自身節(jié)點(diǎn),并喚醒
線程4。

這里可能讀者會(huì)有個(gè)疑問(wèn),線程節(jié)點(diǎn)的狀態(tài)是什么時(shí)候設(shè)置上去的。其實(shí),一個(gè)線程在阻塞之前,就會(huì)把它前面的節(jié)點(diǎn)設(shè)置為通知狀態(tài),這樣便可以實(shí)現(xiàn)鏈?zhǔn)絾拘褭C(jī)制了。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/71402.html

相關(guān)文章

  • 從單例模式到HappensBefore

    摘要:但是有引入了新的問(wèn)題線程不安全,返回的對(duì)象可能還沒(méi)有初始化。如果只有一個(gè)線程調(diào)用是沒(méi)有問(wèn)題的因?yàn)椴还懿襟E如何調(diào)換,保證返回的對(duì)象是已經(jīng)構(gòu)造好了。這種特殊情況稱之為指令重排序采用了允許將多條指令不按程序規(guī)定的順序分開(kāi)發(fā)送給各相應(yīng)電路單元處理。 目錄 雙重檢測(cè)鎖的演變過(guò)程 利用HappensBefore分析并發(fā)問(wèn)題 無(wú)volatile的雙重檢測(cè)鎖 雙重檢測(cè)鎖的演變過(guò)程 synch...

    v1 評(píng)論0 收藏0
  • CopyOnWriteArrayList你都不知道,怎么拿offer?

    摘要:今天主要講解的是本文力求簡(jiǎn)單講清每個(gè)知識(shí)點(diǎn),希望大家看完能有所收獲一和回顧線程安全的和我們知道是用于替代的,是線程安全的容器。使用迭代器遍歷時(shí)不需要顯示加鎖,看看與方法的實(shí)現(xiàn)可能就有點(diǎn)眉目了。 前言 只有光頭才能變強(qiáng) showImg(https://segmentfault.com/img/remote/1460000016931828?w=1120&h=640); 前一陣子寫(xiě)過(guò)一篇C...

    noONE 評(píng)論0 收藏0
  • nodejs中的子進(jìn)程,深入解析child_process模塊和cluster模塊

    摘要:嚴(yán)格來(lái)說(shuō),并不是單線程的。其他異步和事件驅(qū)動(dòng)相關(guān)的線程通過(guò)來(lái)實(shí)現(xiàn)內(nèi)部的線程池和線程調(diào)度。線程是最小的進(jìn)程,因此也是單進(jìn)程的。子進(jìn)程中執(zhí)行的是非程序,提供一組參數(shù)后,執(zhí)行的結(jié)果以回調(diào)的形式返回。在子進(jìn)程中通過(guò)和的機(jī)制來(lái)接收和發(fā)送消息。 ??node遵循的是單線程單進(jìn)程的模式,node的單線程是指js的引擎只有一個(gè)實(shí)例,且在nodejs的主線程中執(zhí)行,同時(shí)node以事件驅(qū)動(dòng)的方式處理IO...

    JinB 評(píng)論0 收藏0
  • 一起學(xué)設(shè)計(jì)模式 - 單例模式

    摘要:懶漢非線程安全,需要用一定的風(fēng)騷操作控制,裝逼失敗有可能導(dǎo)致看一周的海綿寶寶餓漢天生線程安全,的時(shí)候就已經(jīng)實(shí)例化好,該操作過(guò)于風(fēng)騷會(huì)造成資源浪費(fèi)單例注冊(cè)表初始化的時(shí)候,默認(rèn)單例用的就是該方式特點(diǎn)私有構(gòu)造方法,只能有一個(gè)實(shí)例。 單例設(shè)計(jì)模式(Singleton Pattern)是最簡(jiǎn)單且常見(jiàn)的設(shè)計(jì)模式之一,主要作用是提供一個(gè)全局訪問(wèn)且只實(shí)例化一次的對(duì)象,避免多實(shí)例對(duì)象的情況下引起邏輯性錯(cuò)...

    Keven 評(píng)論0 收藏0
  • 手撕面試官系列(七):面試必備之常問(wèn)并發(fā)編程高級(jí)面試專題

    摘要:如何在線程池中提交線程內(nèi)存模型相關(guān)問(wèn)題什么是的內(nèi)存模型,中各個(gè)線程是怎么彼此看到對(duì)方的變量的請(qǐng)談?wù)動(dòng)惺裁刺攸c(diǎn),為什么它能保證變量對(duì)所有線程的可見(jiàn)性既然能夠保證線程間的變量可見(jiàn)性,是不是就意味著基于變量的運(yùn)算就是并發(fā)安全的請(qǐng)對(duì)比下對(duì)比的異同。 并發(fā)編程高級(jí)面試面試題 showImg(https://upload-images.jianshu.io/upload_images/133416...

    Charles 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<