線程安全

線程安全性:

定義:當(dāng)多個線程訪問某個類時,不管允許時環(huán)境采用何種調(diào)度方式或者這些進(jìn)程將如何交替執(zhí)行眨攘,并且在主調(diào)代碼中不需要任何額外的同步或協(xié)同,這些類都能表現(xiàn)出正確的行為,那么就稱為這個類是線程安全的.

  • 原子性:提供互斥訪問递惋,同一時刻只能有一個線程來對它進(jìn)行操作。
  • 可見性:一個線程對主內(nèi)存的修改可以及時的被其他線程觀察到雹顺。
  • 有序性:一個線程觀察其他線程中的指令執(zhí)行順序丹墨,由于指令重排序的存在,該觀察結(jié)果一般雜亂無序嬉愧。

原子性

Atomic包
  • AtomicXXX:CAS贩挣,Unsafe.compareAndSwapInt


    Atomic
    /**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        // 主要是調(diào)用了unsafe的方法 
        // private static final Unsafe unsafe = Unsafe.getUnsafe();
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
/**
  *  獲取底層當(dāng)前的值并且+1
  * @param var1 需要操作的AtomicInteger 對象
  * @param var2 當(dāng)前的值 
  * @param var4 要增加的值
  */
  public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            // 獲取底層的該對象當(dāng)前的值
            var5 = this.getIntVolatile(var1, var2);
            // 獲取完底層的值和自增操作之間,可能系統(tǒng)的值已經(jīng)又被其他線程改變了
            //如果又被改變了没酣,則重新計算系統(tǒng)底層的值王财,并重新執(zhí)行本地方法
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
        return var5;
     }
/** 
  * 本地的CAS方法核心 
  * @param var1 需要操作的AtomicInteger 對象 
  * @param var2 當(dāng)前本地變量中的的值 
  * @param var4 當(dāng)前系統(tǒng)從底層傳來的值 
  * @param var5 要更新后的值 
  * @Return 如果當(dāng)前本地變量的值(var2)與底層的值(var4)不等,則返回false裕便,否則更新為var5的值并返回True 
  */

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
  • AtomicLong绒净、LongAdder

LongAdder的設(shè)計思想:核心是將熱點(diǎn)數(shù)據(jù)分離,將內(nèi)部數(shù)據(jù)value分成一個數(shù)組偿衰,每個線程訪問時挂疆,通過hash等算法映射到其中一個數(shù)字進(jìn)行技術(shù)改览,而最終計數(shù)結(jié)果為這個數(shù)組的求和累加,其中熱點(diǎn)數(shù)據(jù)value會被分離成多個熱點(diǎn)單元的數(shù)據(jù)cell缤言,每個cell獨(dú)自維護(hù)內(nèi)部的值宝当,當(dāng)前value的實(shí)際值由所有的cell累積合成,從而使熱點(diǎn)進(jìn)行了有效的分離胆萧,提高了并行度.
LongAdder 在低并發(fā)的時候通過直接操作base庆揩,可以很好的保證和Atomic的性能基本一致,在高并發(fā)的場景跌穗,通過熱點(diǎn)分區(qū)來提高并行度

缺點(diǎn):在統(tǒng)計的時候如果有并發(fā)更新订晌,可能會導(dǎo)致結(jié)果有些誤差

  • AtomicReference、AtomicReferenceFieldUpdater

AtomicReference: 用法同AtomicInteger一樣蚌吸,但是可以放各種對象

package com.mmall.example.atomic;


import com.mmall.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.LongAdder;

/**
 * 線程安全的 --> AtomicReference
 * Created by megan on 2018/3/18.
 */
@Slf4j
@ThreadSafe
public class AtomicReferenceExample {
    private static AtomicReference<Integer> count = new AtomicReference<>(0);

    public static void main(String[] args) {
        // 2
        count.compareAndSet(0,2);
        // no
        count.compareAndSet(0,1);
        // no
        count.compareAndSet(1,3);
        // 4
        count.compareAndSet(2,4);
        // no
        count.compareAndSet(3,5);
        log.info("count:{}",count);
    }

}

AtomicReferenceFieldUpdater:

package com.mmall.example.atomic;


import com.mmall.annoations.ThreadSafe;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

/**
 * 線程安全的 --> AtomicReferenceFieldUpdater
 * Created by megan on 2018/3/18.
 */
@Slf4j
@ThreadSafe
public class AtomicReferenceFieldUpdaterExample {

    private static AtomicIntegerFieldUpdater<AtomicReferenceFieldUpdaterExample> updater =
            AtomicIntegerFieldUpdater.newUpdater(AtomicReferenceFieldUpdaterExample.class, "count");

    @Getter
    private volatile int count = 100;


    public static void main(String[] args) {

        AtomicReferenceFieldUpdaterExample example =  new AtomicReferenceFieldUpdaterExample();

        if(updater.compareAndSet(example,100,200)){
            log.info("update success 1 , {}",example.getCount());
        }

        if(updater.compareAndSet(example,100,200)){
            log.info("update success 2 , {}",example.getCount());
        }else{
            log.info("update failed ,{}",example.getCount());
        }
    }

}

  • AtomicStampReference:CAS的ABA問題

ABA問題:在CAS操作的時候锈拨,其他線程將變量的值A(chǔ)改成了B由改成了A,本線程使用期望值A(chǔ)與當(dāng)前變量進(jìn)行比較的時候套利,發(fā)現(xiàn)A變量沒有變推励,于是CAS就將A值進(jìn)行了交換操作,這個時候?qū)嶋H上A值已經(jīng)被其他線程改變過肉迫,這與設(shè)計思想是不符合的

解決思路:每次變量更新的時候验辞,把變量的版本號加一,這樣只要變量被某一個線程修改過喊衫,該變量版本號就會發(fā)生遞增操作跌造,從而解決了ABA變化

  • AtomicBoolean
package com.mmall.example.atomic;


import com.mmall.annoations.ThreadSafe;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

/**
 * 線程安全的 --> AtomicBoolean
 * 5000次請求,test方法只執(zhí)行一次
 * Created by megan on 2018/3/18.
 */
@Slf4j
@ThreadSafe
public class AtomicBooleanExample {

    private static AtomicBoolean isHappened  =  new AtomicBoolean(false);

    /** 請求數(shù) **/
    public static int clientTotal = 5000;

    /** 同時并發(fā)執(zhí)行線程數(shù) **/
    public static int threadTotal = 200;

    public static void main(String[] args) throws Exception {
        //定義一個線程池
        ExecutorService executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                60L, TimeUnit.SECONDS,
                new SynchronousQueue<>(), r -> new Thread(r,"測試線程AtomicBoolean"));
        // 信號量族购,閉鎖
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        // 模擬并發(fā)請求
        for(int i = 0; i < clientTotal; i ++){
            executorService.execute(() -> {
                try {
                    // 請求一個信號壳贪,如果信號量小于clientTotal,則阻塞
                    semaphore.acquire();
                    test();
                    // 釋放一個信號
                    semaphore.release();
                } catch (InterruptedException e) {
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        // 阻塞直到countDown 的次數(shù)為threadTotal
        countDownLatch.await();
        // 關(guān)閉線程池
        executorService.shutdown();
        log.info("isHappened:{}",isHappened);
    }

    private static void test(){
        if(isHappened.compareAndSet(false,true)){
            log.info("execute");
        }
    }

}

原子性-鎖
  • synchronized:依賴JVM (主要依賴JVM實(shí)現(xiàn)鎖寝杖,因此在這個關(guān)鍵字作用對象的作用范圍內(nèi)违施,都是同一時刻只能有一個線程進(jìn)行操作的)
1、修飾代碼塊:大括號括起來的代碼瑟幕,作用于調(diào)用的對象
2磕蒲、修飾方法,作用于調(diào)用的對象
3只盹、修飾靜態(tài)方法:整個靜態(tài)方法辣往,作用于所有對象
4、修飾類:括號包起來的部分殖卑,作用于所有對象
package com.mmall.example.sync;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.*;

/**
 * Synchronized 鎖
 * Created by megan on 2018/3/21.
 */
@Slf4j
public class SynchronizedExample1 {

    /**
     * 修飾代碼塊,作用范圍為大括號括起來的
     */
    public void test1(int j){
        synchronized (this){
            for(int i = 0,b = 10; i<b ; i++){
                log.info("test1 {} --> {}",j,i);
            }
        }
    }

    /**
     * 修飾整個方法站削,作用范圍是整個方法,作用對象為調(diào)用這個方法的對象
     * 若子類繼承父類調(diào)用父類的synchronized方法孵稽,是帶不上synchronized關(guān)鍵字的
     * 原因:synchronized 不屬于方法聲明的一部分
     * 如果子類也想使用同步需要在方法上聲明
     */
    public synchronized void test2(int j){
        for(int i = 0,b = 10; i<b ; i++){
            log.info("test2 {} --> {}",j,i);
        }
    }

    public static void main(String[] args) {
        SynchronizedExample1 example1 = new SynchronizedExample1();
        SynchronizedExample1 example2 = new SynchronizedExample1();
        //聲明一個線程池
        ExecutorService executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                60L, TimeUnit.SECONDS,
                new SynchronousQueue<>(), r -> new Thread(r,"測試線程(Synchronized1)"));
        executorService.execute(() -> {
            example1.test2(1);
        });
        executorService.execute(() -> {
            example2.test2(2);
        });
    }
}
package com.mmall.example.sync;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Synchronized 鎖
 * Created by megan on 2018/3/21.
 */
@Slf4j
public class SynchronizedExample2 {

    /**
     * 修飾類
     * 作用對象為這個類的所有對象
     */
    public static void test1(int j){
        synchronized (SynchronizedExample2.class){
            for(int i = 0,b = 10; i<b ; i++){
                log.info("test1 {} --> {}",j,i);
            }
        }
    }

    /**
     * 修飾靜態(tài)方法
     * 作用對象為這個類的所有對象
     */
    public static synchronized void test2(int j){
        for(int i = 0,b = 10; i<b ; i++){
            log.info("test2 {} --> {}",j,i);
        }
    }

    public static void main(String[] args) {
        //聲明一個線程池
        ExecutorService executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                60L, TimeUnit.SECONDS,
                new SynchronousQueue<>(), r -> new Thread(r,"測試線程(Synchronized1)"));
        executorService.execute(() -> {
            test2(1);
        });
        executorService.execute(() -> {
            test2(2);
        });
    }
}
  • Lock:依賴特殊的CPU指令许起,代碼實(shí)現(xiàn)十偶,ReentrantLock
對比:
  • synchronized:不可中斷,適合競爭不激烈园细,可讀性號
  • Lock:可中斷扯键,多樣化同步,競爭激烈是能維持常態(tài)
  • Atomic:競爭激烈時能維持常態(tài)珊肃,比lock性能好;只能同步一個值馅笙。

可見性(java提供了synchronized和volatile 兩種方法來確甭浊牵可見性)

導(dǎo)致線程共享變量在線程間不可見的原因:

  • 線程交叉執(zhí)行
  • 重排序結(jié)合線程交叉執(zhí)行
  • 共享變量更新后的值沒有在工作內(nèi)存與主內(nèi)存間及時更新

JMM關(guān)于synchronized的兩條規(guī)定:

  • 線程解鎖前,必須把共享變量的最小值刷新到主內(nèi)存中
  • 線程加鎖是董习,將清空工作內(nèi)存中共享變量的值烈和,從而使共享變量時需要從主內(nèi)存中重新讀取最新的值(注意,加鎖與解鎖是同一把鎖)
volatile

通過加入內(nèi)存屏障和禁止重排序優(yōu)化來實(shí)現(xiàn)可見性皿淋。

  • 對于volatile變量寫操作時招刹,會在寫操作后加入一條store屏障指令,將本地內(nèi)存中的共享變量值刷新到主內(nèi)存窝趣。
  • 對volatile變量度操作時疯暑,會在讀操作前加入一條load屏障指令,從主內(nèi)存中讀取共享變量哑舒。


    volatile寫操作時

    volatile讀操作時

    volatile使用條件:
    1妇拯、對變量寫操作不依賴于當(dāng)前值
    2、該變量沒有包含在具有其他變量的不必要的式子中

綜上洗鸵,volatile特別適合用來做線程標(biāo)記量越锈,如下圖


volatile使用

有序性

java內(nèi)存模型中,允許編譯器和處理器對指令進(jìn)行重排序膘滨,但是甘凭,重排序過程劊影響到單線程的執(zhí)行,卻會影響到多線程并發(fā)執(zhí)行的正確性火邓。

  • volatile丹弱、synchronized、lock
happens-before原則
  • 程序次序規(guī)則:一個線程內(nèi)贡翘,按照代碼順序蹈矮,書寫在前面的操作先行發(fā)生于書寫在后面的操作。
  • 鎖定規(guī)則: 一個unLock操作先行發(fā)生于后面對同一個鎖的lock操作鸣驱。
  • volatile變量規(guī)則:對一個變量的寫操作先行發(fā)生于后面對于這個變量的讀操作泛鸟。
  • 傳遞規(guī)則: 對于操作A先行發(fā)生于操作B,而操作B又先行發(fā)生于操作C踊东,則可以得出操作A先行發(fā)生于操作C北滥。
  • 線程啟動規(guī)則: Thread對于start()方法先行發(fā)生于此線程的每個動作刚操。
  • 線程中斷規(guī)則: 對線程interrupt()方法的調(diào)用先行發(fā)生于被中斷線程的代碼檢測到中斷事件的發(fā)生。
  • 線程終結(jié)規(guī)則: 線程中所有的操作都先行發(fā)生于線程的終止檢測再芋,我們可以通過Tread.join()方法結(jié)束菊霜、Thread.isAlive()的返回值手段檢測到線程已經(jīng)終止執(zhí)行
  • 對象終結(jié)原則: 一個對象的初始化完成先行發(fā)生于他的finalize()方法的開始.
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市济赎,隨后出現(xiàn)的幾起案子鉴逞,更是在濱河造成了極大的恐慌,老刑警劉巖司训,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件构捡,死亡現(xiàn)場離奇詭異,居然都是意外死亡壳猜,警方通過查閱死者的電腦和手機(jī)勾徽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來统扳,“玉大人喘帚,你說我怎么就攤上這事≈渲樱” “怎么了吹由?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長盯腌。 經(jīng)常有香客問我溉知,道長,這世上最難降的妖魔是什么腕够? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任级乍,我火速辦了婚禮,結(jié)果婚禮上帚湘,老公的妹妹穿的比我還像新娘玫荣。我一直安慰自己,他們只是感情好大诸,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布捅厂。 她就那樣靜靜地躺著,像睡著了一般资柔。 火紅的嫁衣襯著肌膚如雪焙贷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天贿堰,我揣著相機(jī)與錄音辙芍,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛故硅,可吹牛的內(nèi)容都是我干的庶灿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼吃衅,長吁一口氣:“原來是場噩夢啊……” “哼往踢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起徘层,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤峻呕,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后趣效,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體山上,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年英支,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哮伟。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡干花,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出楞黄,到底是詐尸還是另有隱情池凄,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布鬼廓,位于F島的核電站肿仑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏碎税。R本人自食惡果不足惜尤慰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望雷蹂。 院中可真熱鬧伟端,春花似錦、人聲如沸匪煌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽萎庭。三九已至霜医,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間驳规,已是汗流浹背肴敛。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留达舒,地道東北人值朋。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓叹侄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親昨登。 傳聞我的和親對象是個殘疾皇子趾代,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容