線程安全

線程安全問題

當多個線程同時共享同一個全局變量或靜態(tài)變量,做寫操作時卫玖,可能會發(fā)生數(shù)據(jù)沖突問題公你,也就是線程安全問題。但是做讀操作是不會發(fā)生數(shù)據(jù)沖突問題假瞬。

例子:現(xiàn)在有100張火車票陕靠,有兩個窗口同時搶火車票,請使用多線程擬搶票效果脱茉。

class Demo2 implements Runnable {
    private int count = 100;
    private static Object oj = new Object();

    @Override 
    public void run() {
        while (count > 0) {
            try {
                Thread.sleep(50);
            } catch (Exception e) {
                // TODO: handle exception
            }
            sale();
        }
    }

    public void sale() {
        // 前提 多線程進行使用剪芥、多個線程只能拿到一把鎖。
        // 保證只能讓一個線程 在執(zhí)行 缺點效率降低
        // synchronized (oj) {
//      if (count > 0) {
            System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
            count--;
//      }
        // }
    }

    public static void main(String[] args) {
        Demo2 threadTrain1 = new Demo2();
        Thread t1 = new Thread(threadTrain1, "①號窗口");
        Thread t2 = new Thread(threadTrain1, "②號窗口");
        t1.start();
        t2.start();
    }
}

運行結(jié)果:

運行結(jié)果.png

結(jié)果發(fā)現(xiàn)琴许,多個線程共享同一個全局成員變量時税肪,做寫的操作可能會發(fā)生數(shù)據(jù)沖突問題。

線程安全解決辦法

1.如何解決多線程之間的線程安全問題榜田?
使用多線程之間同步synchronized或使用鎖(lock)益兄。
2.為什么使用線程同步或使用鎖能解決線程安全問題?
將可能會發(fā)生數(shù)據(jù)沖突問題(線程不安全問題)箭券,只能讓當前一個線程進行執(zhí)行净捅。代碼執(zhí)行完成后釋放鎖,然后才能讓其他線程進行執(zhí)行辩块。這樣的話就可以解決線程不安全問題蛔六。
3.什么是多線程之間同步荆永?
當多個線程共享一個資源,不會受到其他線程的干擾国章。

同步代碼塊

同步代碼塊就是將可能會發(fā)生線程安全問題的代碼具钥,給包括起來。

synchronized(同一個數(shù)據(jù)){
  可能會發(fā)生線程沖突問題
}

synchronized(對象){//這個對象可以為任意對象
  需要被同步的代碼
}

對象如同鎖液兽,持有鎖的線程可以在同步中執(zhí)行骂删,沒持有鎖的線程即使獲取CPU的執(zhí)行權(quán)也進不去。
同步的前提:
1.必須要有兩個或者兩個以上的線程抵碟。
2.必須是多個線程使用同一個鎖桃漾,保證同步中只能有一個線程在運行坏匪。
好處:解決了多線程的安全問題拟逮。
弊端:多個線程需要判斷所,比較小號資源适滓、搶鎖的資源敦迄。

public class Demo1 implements Runnable{
    private int count =100;
    private static Object obj = new Object();
    
    @Override
    public void run() {
        while(count>0) {
            
        try {
            Thread.sleep(50);
        } catch (Exception e) {
            // TODO: handle exception
        }
        sale();
        }
    }
    
    public void sale() {
        synchronized (obj) {
            if(count>0) {
        System.out.println(Thread.currentThread().getName()+",出售第"+(100-count+1)+"票");
        count--;
        }
        }
    }
    
    public static void main(String[] args) {
        Demo1 threadTrain = new Demo1();
        Thread t1 = new Thread(threadTrain,"1號窗口");
        Thread t2 = new Thread(threadTrain,"2號窗口");
        t1.start();
        t2.start();
    }
}

同步函數(shù)函數(shù)this鎖凭迹。
public synchronized void sale(){
 if(count>0){
 try{
      Thread.sleep(40);
    }catch(Exception e){
      
    }
    System.out.prinln(.....);
    count--;
  }
}
靜態(tài)同步函數(shù)

方法上加上static關(guān)鍵字罚屋,使用synchronized關(guān)鍵字修飾或者使用類.class文件。
靜態(tài)的同步函數(shù)使用的鎖是該函數(shù)所屬字節(jié)碼文件對象嗅绸。
可以用getClass方法獲取脾猛,也可以用當前類名.class表示。

synchronized(demo1.class){
  System.....;
  count--;
  .....
}

總結(jié):synchronized修飾方法使用鎖是當前this鎖鱼鸠。
synchronized修飾靜態(tài)方法使用鎖是當前類的字節(jié)碼文件猛拴。

多線程死鎖

同步中嵌套同步,導(dǎo)致鎖無法釋放會導(dǎo)致死鎖

多線程有三大特性

原子性蚀狰、可見性愉昆、有序性

原子性
一個操作或多個操作 要么全部執(zhí)行并且執(zhí)行的過程中不會被任何因素打斷,要么就都不執(zhí)行麻蹋。
一個很經(jīng)典的例子就是銀行賬戶轉(zhuǎn)賬問題:
比如從賬戶A向賬戶B轉(zhuǎn)1000元跛溉,那么必然包括2個操作:從賬戶A減去1000元,往賬戶B加上1000元扮授。這2個操作具備原子性才能保證不出現(xiàn)一些意外的問題芳室。
我們操作數(shù)據(jù)也是如此,比如i=i+1;其中就包括刹勃,讀取i的值堪侯,計算i,寫入i深夯。這行代碼在Java中是不具備原子性的抖格,多線程運行肯定會出問題诺苹,所以也需要我們使用同步和lock這些東西來確保這個特性了。
原子性其實就是保證數(shù)據(jù)一致雹拄、線程安全一部分收奔。

可見性
當多個線程訪問同一個變量時,一個線程修改了這個變量的值滓玖,其他線程能夠立即看到修改的值坪哄。
若兩個線程在不同的CPU,那么線程1改變了i的值還沒刷新到主存势篡,線程2又使用了i翩肌,那么這個i值肯定還是之前的,線程1對變量的修改線程沒看到這就是可見性禁悠。

可見性.png

從上圖來看念祭,線程A與線程B之間如果要通信的話,必須要經(jīng)歷下面2個步驟:
1.線程A把本地內(nèi)存A中更新過的共享變量刷新到主內(nèi)存中去碍侦。
2.線程B到主內(nèi)存中去讀取線程A之前已更新過的共享變量粱坤。
可見性1.png

如上圖所示,本地內(nèi)存A和B有主內(nèi)存中共享變量x的副本瓷产。假設(shè)初始時站玄,這三個內(nèi)存中的x都為0,線程A在執(zhí)行時濒旦,把更新后的x(假設(shè)值為1)臨時存放在自己的本地內(nèi)存A中株旷。當線程A和線程B需要通信時,線程A首先會把自己本地內(nèi)存中修改后的x值刷新到主內(nèi)存中尔邓,此時主內(nèi)存中的x值變?yōu)榱?晾剖。隨后,線程B到主內(nèi)存中去讀取線程A更新后的x值铃拇,此時線程B的本地內(nèi)存的x值也變成1钞瀑。

從整體來看,這2個步驟是指上是線程A在向線程B發(fā)送消息慷荔,而且這個通信過程必須要經(jīng)過主內(nèi)存雕什。JMM通過控制主內(nèi)存與每個線程的本地內(nèi)存之間的交互,來為java程序要提供內(nèi)存可見性保證显晶。

總結(jié):jmm就是java內(nèi)存模型,定義了一個線程對另一個線程可見贷岸。共享變量存放在主內(nèi)存中,每個線程都有自己的本地內(nèi)存磷雇,當多個線程同時訪問一個數(shù)據(jù)的時候偿警,可能本地內(nèi)存沒有及時刷新到主內(nèi)存,所以就會發(fā)生線程安全問題唯笙。

有序性
程序執(zhí)行的順序按照代碼的先后順序執(zhí)行螟蒸。
一般來說處理器為了提高程序運行效率盒使,可能會對輸入代碼進行優(yōu)化,它不保證程序中各個語句的執(zhí)行先后順序同代碼中的順序一致七嫌,但是它會保證程序最終執(zhí)行結(jié)果和代碼順序執(zhí)行的結(jié)果是一直的少办。如下:
int a = 1;//語句1
int r = 2;//語句2
a = a+3; //語句3
r = a*a;//語句4
則因為重排序,它還可能執(zhí)行順序為2-1-3-4诵原,1-3-2-4
但絕不可能2-1-4-3英妓,因為這打破了依賴關(guān)系。
顯然重排序?qū)尉€程運行時不會有任何問題绍赛,而多線程就不一定了蔓纠,所以我們在多線程變成時就得考慮這個問題了。

Volatile

volatile關(guān)鍵字的作用是變量在多個線程之間可見

class Demo extends Thread{
    public boolean flag = true;
    @Override
    public void run() {
        System.out.println("開始執(zhí)行子線程");
        while(flag) {
        }
        System.out.println("線程停止");
    }
    public void setRuning(boolean flag) {
        this.flag = flag;
    }
}
    
    public class Demo1{
        public static void main(String[] args) throws InterruptedException {
            Demo demo = new Demo();
            demo.start();
            Thread.sleep(3000);
            demo.setRuning(false);
            System.out.println("flag已經(jīng)設(shè)置成false");
            Thread.sleep(1000);
            System.out.println(demo.flag);
        }
    }

在沒有設(shè)置延遲的時候flag改為false后線程能夠順利結(jié)束吗蚌,但是加入延遲后程序一直在運行沒有結(jié)束腿倚,原因是線程之間是不可見的,讀取的是副本褪测,沒有及時讀取到主內(nèi)存的結(jié)果猴誊。
解決辦法就是在變量flag之前添加關(guān)鍵字volatile解決線程之間的可見性,強制線程每次讀取該值的時候都去主內(nèi)存中取值侮措。

volatile非原子性

public class VolatileNoAtomic extends Thread{
    public static volatile int count;
    private static void addCount() {
        for (int i = 0; i < 1000; i++) {
            count++;
        }
        System.out.println(count);
}
    
    public void run() {
        addCount();
    }
    
//  public class Demo1{
        public static void main(String[] args) {
            VolatileNoAtomic[] arr = new VolatileNoAtomic[100];
            for (int i = 0; i < 10; i++) {
                arr[i] = new VolatileNoAtomic();
            }
            for (int i = 0; i < 10; i++) {
                arr[i].start();
            }
        }
    }

多運行幾次會發(fā)現(xiàn)最大值有小概率不是10000,數(shù)據(jù)不同步乖杠,說明volatile不具備原子性分扎。
AtomicInteger是一個提供院子操作的Integer類,通過線程安全的方式操作加減胧洒。

public class VolatileNoAtomic extends Thread{
//  public static volatile int count=0;
    private static AtomicInteger atomicInteger = new AtomicInteger(0);

    private static void addCount() {
        for (int i = 0; i < 1000; i++) {
            //等同于i++
            atomicInteger.incrementAndGet();
        }
        System.out.println(atomicInteger.get());
}
    
    public void run() {
        addCount();
    }
    
//  public class Demo1{
        public static void main(String[] args) {
            VolatileNoAtomic[] arr = new VolatileNoAtomic[10];
            for (int i = 0; i < 10; i++) {
                arr[i] = new VolatileNoAtomic();
            }
            for (int i = 0; i < arr.length; i++) {
                arr[i].start();
            }
        }
    }
volatile與synchronized區(qū)別

僅僅靠volatile不能保證線程的安全性畏吓。
1.volatile輕量級,只能修飾變量卫漫。synchronized重量級菲饼,還可以修飾方法。
2.volatile只能保證數(shù)據(jù)的可見性列赎,不能用來同步宏悦,因為多個線程并發(fā)訪問volatile修飾的變量不會阻塞。
synchronized不僅保證可見性包吝,而且還保證原子性饼煞。因為,只有獲得了鎖的線程才能進入臨界區(qū)诗越,從而保證臨界區(qū)中的所有語句都全部執(zhí)行砖瞧。多個線程爭搶synchronized鎖對象時,會出現(xiàn)阻塞嚷狞。

線程安全性

線程安全性包括兩個方面1.可見性块促。2.原子性荣堰。
從上面自增的例子中可以看出,僅僅使用volatile不能保證線程安全性竭翠。而synchronized則可實現(xiàn)線程的安全性持隧。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市逃片,隨后出現(xiàn)的幾起案子屡拨,更是在濱河造成了極大的恐慌,老刑警劉巖褥实,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件呀狼,死亡現(xiàn)場離奇詭異,居然都是意外死亡损离,警方通過查閱死者的電腦和手機哥艇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來僻澎,“玉大人貌踏,你說我怎么就攤上這事】卟” “怎么了祖乳?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長秉氧。 經(jīng)常有香客問我眷昆,道長,這世上最難降的妖魔是什么汁咏? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任月培,我火速辦了婚禮整陌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己猬错,他們只是感情好吱涉,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布潘鲫。 她就那樣靜靜地躺著震糖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪级解。 梳的紋絲不亂的頭發(fā)上冒黑,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天,我揣著相機與錄音勤哗,去河邊找鬼抡爹。 笑死,一個胖子當著我的面吹牛芒划,可吹牛的內(nèi)容都是我干的冬竟。 我是一名探鬼主播欧穴,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼泵殴!你這毒婦竟也來了涮帘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤笑诅,失蹤者是張志新(化名)和其女友劉穎调缨,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吆你,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡弦叶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了妇多。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伤哺。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖者祖,靈堂內(nèi)的尸體忽然破棺而出立莉,到底是詐尸還是另有隱情,我是刑警寧澤七问,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布蜓耻,位于F島的核電站,受9級特大地震影響烂瘫,放射性物質(zhì)發(fā)生泄漏媒熊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一坟比、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嚷往,春花似錦葛账、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至贷祈,卻和暖如春趋急,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背势誊。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工呜达, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人粟耻。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓查近,卻偏偏與公主長得像眉踱,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子霜威,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

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