Java并發(fā)中常見概念

本文主要記錄自己閱讀《Java并發(fā)編程實(shí)戰(zhàn)》后,對(duì)并發(fā)編碼的淺薄認(rèn)識(shí),為原創(chuàng)內(nèi)容,如有文中有書寫或其他問題,請(qǐng)留言指導(dǎo)修正,互相交流,共同進(jìn)步,本人QQ:417213902。

常見的并發(fā)概念

  • 原子性

    符合原子操作的那么就說具有原子性,那么原子操作指不會(huì)被線程調(diào)度
    機(jī)制打斷的操作;這種操作一旦開始,就一直運(yùn)行到結(jié)束,中間不會(huì)有
    任何上下文切換。
    

    比如我們常見的++a,它的操作是原子的,因?yàn)樗⒉粫?huì)作為一個(gè)不可
    分割的操作來執(zhí)行,這是一個(gè)“讀取-修改-寫入”的操作序列,并且其結(jié)果
    狀態(tài)依賴于之前的狀態(tài)。
    我們可以通過采用java.util.concurrent.atomic包中包含的原子變量類,用于實(shí)現(xiàn)在數(shù)值和對(duì)象引用上的原子狀態(tài)轉(zhuǎn)換。原子變量類采用了CAS(compare and swap)無鎖算法,屬于樂觀鎖。CAS的原理是有3個(gè)操作數(shù),內(nèi)存值V,舊的預(yù)期值A(chǔ),要修改的新值B,當(dāng)且僅當(dāng)A和V相等時(shí),將V改為B,否則什么也不做。
    還有一種方式可以采用加鎖機(jī)制,即內(nèi)置鎖及重入機(jī)制,用關(guān)鍵字synchronized同步代碼塊能達(dá)到原子操作。

  • 競(jìng)態(tài)條件

    在并發(fā)編程中,當(dāng)某個(gè)計(jì)算的正確性取決于多個(gè)線程的交替執(zhí)行時(shí)序
    時(shí),那么就會(huì)發(fā)生競(jìng)態(tài)條件。
    

    比如說“先檢查后執(zhí)行”操作,即可能通過一個(gè)可能失效的觀測(cè)結(jié)果來決定
    下一步的動(dòng)作。常見的有單例模式。那么此時(shí)我們采用可以原子操作解決此問題,即保證在單例模式中初始化變量的方法增加鎖synchronized,或者定義原子性變量。

  • 指令重排序

     在沒有充分同步的程序中,如果調(diào)度器采用不恰當(dāng)?shù)姆绞絹斫惶鎴?zhí)行
     不同線程的操作,那么將導(dǎo)致不正確的結(jié)果,更糟的是,JVM還使得不
     同線程看到的操作執(zhí)行順序是不同,從而導(dǎo)致在缺乏同步的情況下,要
     推斷操作的執(zhí)行順序?qū)⒆兊酶訌?fù)雜,此些都可以歸為重排序。
    

    在編譯器中生成的指令順序,可以與源代碼中的順序不同,此外編譯器還會(huì)把變量保存在寄存器而不是內(nèi)存中;處理器可以采用亂序或并行等方式執(zhí)行指令;緩存可能會(huì)改變將寫入變量提交到主內(nèi)存的次序;而且,保存在處理器本地緩存中值,對(duì)于其他處理器是不可見的。當(dāng)然對(duì)于亂序,我覺得應(yīng)該是沒有依賴或者關(guān)聯(lián)的指令可以亂序。

  • 可見性

    當(dāng)多個(gè)線程同時(shí)訪問同一個(gè)變量時(shí),一個(gè)線程修改了這個(gè)變量的值,其 
    他線程能夠立即看得到修改的值。
    

    首先我們得要理解下,在實(shí)際內(nèi)存中,每個(gè)線程內(nèi)都會(huì)有個(gè)工作內(nèi)存,僅每個(gè)線程自己可見,且還有共享內(nèi)存,工作內(nèi)存中的值由共享內(nèi)存復(fù)制過來;當(dāng)把變量聲明為volatile類型后,線程每次操作都是修改主內(nèi)存中的變量,讀取volatile類型變量時(shí)也總是返回最新寫入的值。這就說明volatile能解決 線程對(duì)變量的可見性,注意volatile不可以對(duì)非原子操作的變量具有可見性。加鎖機(jī)制既可以確??梢娦杂挚梢源_保原子性,而volatile變量只能確??梢娦?。synchronized同樣可以保證可見性。

  • 不可變

    如果某個(gè)對(duì)象在被創(chuàng)建后其狀態(tài)就不能被修改,那么這個(gè)對(duì)象就稱為不可變對(duì)象。
    

    主要關(guān)鍵字為final。

  • 內(nèi)置鎖及重入性
    java本身提供了同步代碼塊,由關(guān)鍵字synchronized實(shí)現(xiàn),每個(gè)java對(duì)象都可以用做一個(gè)實(shí)現(xiàn)同步的鎖,這些鎖我們稱為內(nèi)置鎖。java的內(nèi)置鎖相當(dāng)于一個(gè)互斥體,這意味著最多只有一個(gè)線程能持有這種鎖,當(dāng)線程A嘗試獲取由線程B持有的鎖時(shí),線程A必須等待或阻塞。
    在java內(nèi)部,同一線程在調(diào)用自己類中其他synchronized方法/塊或調(diào)用父類的synchronized方法/塊都不會(huì)阻礙該線程的執(zhí)行,就是說同一線程對(duì)同一個(gè)對(duì)象鎖是可重入的,而且同一個(gè)線程可以獲取同一把鎖多次,也就是可以多次重入。重入意味著獲取鎖的操作的粒度是“線程”,而不是“調(diào)用”。重入的一種實(shí)現(xiàn)方法是,為每個(gè)鎖關(guān)聯(lián)一個(gè)獲取計(jì)數(shù)值和一個(gè)所有者線程,當(dāng)計(jì)數(shù)值為0時(shí),這個(gè)鎖就被認(rèn)為是沒有被任何線程持有,當(dāng)線程請(qǐng)求一個(gè)未被持有的鎖時(shí),JVM將記下鎖的持有者,并且將獲取計(jì)數(shù)值置為1。如果同一個(gè)線程再次獲取這個(gè)鎖,計(jì)數(shù)值將遞增,而當(dāng)線程退出 同步代碼塊時(shí),計(jì)數(shù)器會(huì)相應(yīng)地遞減,當(dāng)計(jì)數(shù)值為0時(shí),這個(gè)鎖將被釋放。

  • 并發(fā)中常見關(guān)鍵字synchronized、volatile、final
    synchronized :
    修飾一個(gè)方法,被修飾的方法稱為同步方法,其作用的范圍是整個(gè)方法,作用的對(duì)象是調(diào)用這個(gè)方法的對(duì)象;
    synchronized(this)同步代碼塊時(shí),代碼塊為原子操作,多線程將被阻塞。
    synchronized(某個(gè)對(duì)象)同步代碼塊時(shí),這個(gè)對(duì)象將只能被擁有鎖的線程修改
    volatile:
    volatile是Java虛擬機(jī)提供的輕量級(jí)的同步機(jī)制。關(guān)于volatile的可見性作用,我們必須意識(shí)到被volatile修飾的變量對(duì)所有線程總是立即可見的,對(duì)volatile變量的所有寫操作總是能立刻反應(yīng)到其他線程中,但是對(duì)于volatile變量運(yùn)算操作(如a++)在多線程環(huán)境并不保證安全性,這是因?yàn)閍++操作是非原子操作,可以將該方法用synchronized修飾。

  /**
 * Created by zejian on 2017/6/11.
 * Blog : http://blog.csdn.net/javazejian [原文地址,請(qǐng)尊重原創(chuàng)]
 */
public class DoubleCheckLock {
    private static DoubleCheckLock instance;
    private DoubleCheckLock(){}
    public static DoubleCheckLock getInstance(){
        //第一次檢測(cè)
        if (instance==null){
            //同步
            synchronized (DoubleCheckLock.class){
                if (instance == null){
                    //多線程環(huán)境下可能會(huì)出現(xiàn)問題的地方
                    instance = new DoubleCheckLock();
                }
            }
        }
        return instance;
    }
}

上述代碼一個(gè)經(jīng)典的單例的雙重檢測(cè)的代碼,這段代碼在單線程環(huán)境下并沒有什么問題,但如果在多線程環(huán)境下就可以出現(xiàn)線程安全問題。原因在于某一個(gè)線程執(zhí)行到第一次檢測(cè),讀取到的instance不為null時(shí),instance的引用對(duì)象可能沒有完成初始化。因?yàn)閕nstance = new DoubleCheckLock();可以分為以下3步完成(偽代碼)

memory = allocate(); //1.分配對(duì)象內(nèi)存空間
instance(memory);    //2.初始化對(duì)象
instance = memory;   //3.設(shè)置instance指向剛分配的內(nèi)存地址,此時(shí)instance!=null
由于步驟1和步驟2間可能會(huì)重排序,如下:
memory = allocate(); //1.分配對(duì)象內(nèi)存空間
instance = memory;   //3.設(shè)置instance指向剛分配的內(nèi)存地址,此時(shí)instance!=null,但是對(duì)象還沒有初始化完成!
instance(memory);    //2.初始化對(duì)象

由于步驟2和步驟3不存在數(shù)據(jù)依賴關(guān)系,而且無論重排前還是重排后程序的執(zhí)行結(jié)果在單線程中并沒有改變,因此這種重排優(yōu)化是允許的。但是指令重排只會(huì)保證串行語義的執(zhí)行的一致性(單線程),但并不會(huì)關(guān)心多線程間的語義一致性。所以當(dāng)一條線程訪問instance不為null時(shí),由于instance實(shí)例未必已初始化完成,也就造成了線程安全問題。那么該如何解決呢,很簡(jiǎn)單,我們使用volatile禁止instance變量被執(zhí)行指令重排優(yōu)化即可。
final:
final修飾的對(duì)象可以在定義時(shí)或構(gòu)造器中初始化
static final修飾的對(duì)象表示常量,只有在定義時(shí)賦值

除非需要某個(gè)域是可變的,否則應(yīng)將其聲明為final域,也是一個(gè)良好的編程習(xí)慣。摘自《Java并發(fā)編程實(shí)戰(zhàn)》

2018-05-08 23:31:00

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,787評(píng)論 11 349
  • 第2章 java并發(fā)機(jī)制的底層實(shí)現(xiàn)原理 Java中所使用的并發(fā)機(jī)制依賴于JVM的實(shí)現(xiàn)和CPU的指令。 2.1 vo...
    kennethan閱讀 1,525評(píng)論 0 2
  • 張董, 你知道怎樣問女生要手機(jī)嗎? 或者要-Q-?微博? 首先,講一點(diǎn),要手機(jī)是最好的,現(xiàn)在的-微-信-什么都和手...
    張董007閱讀 836評(píng)論 0 0
  • 我其實(shí)是一個(gè)偽好學(xué)者,不管別人怎么說我多少好話,但這一點(diǎn)我還是有比較清醒的認(rèn)識(shí)。常說人貴有自知之明,我這有點(diǎn)恬不知...
    夜半鳴閱讀 258評(píng)論 0 1
  • 9月19日下午,剛進(jìn)入陜西省寶雞第一中學(xué)就讀的馮駿馳收到了姥姥寫給他的信,信封上署名“王老婆”的信足足6頁紙,老人...
    晴川歷歷t閱讀 1,261評(píng)論 4 13

友情鏈接更多精彩內(nèi)容