Java基礎之執行緒安全

麥克斯維發表於2019-02-20

回顧

在上一篇 Java基礎之多執行緒程式設計,我們講解了多執行緒的實現,執行起來似乎也沒什麼問題,但是我們若加一段程式碼

class Window implements Runnable{//實現介面
    int ticket=100;
    @Override
    public void run() {
        while (true){
            if(ticket>0){
                try {
                    Thread.currentThread().sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"售票:票號為:"+ ticket--);
            }else{
                break;
            }
        }
    }
}
Window w=new Window();
        Thread t1=new Thread(w);
        Thread t2=new Thread(w);
        Thread t3=new Thread(w);
        t1.setName("視窗1");
        t2.setName("視窗2");
        t1.start();
        t2.start();
複製程式碼

相比與上一篇我們的程式碼裡多了

Thread.currentThread().sleep(10);
複製程式碼

sleep()方法使得當前執行緒阻塞10毫秒,我們看程式碼執行效果

Java基礎之執行緒安全
票賣超了,怎麼回事?按程式碼邏輯來看,好像並沒什麼問題?

這就是存在 執行緒不安全

問題分析

那麼我們分析下如何出現的這個現象? 我們假設有兩個賣票執行緒:執行緒A和執行緒B ,此時餘票還有1張,看下圖

Java基礎之執行緒安全

Java基礎之執行緒安全

文字解釋下,當ticket=1時,執行緒A進入if判斷內,接著執行緒A進入sleep狀態,緊接著執行緒B獲得cpu執行權開始執行, 此時ticket=1,進入if判斷內 也開始sleep,然後執行緒A的sleep結束 恢復,開始執行,並把ticket--,此時ticket=0,然後執行緒B恢復, 列印tickect為0,ticket再次-1.變成了-1. 這就時三個視窗同時賣票,票賣超的原因,也稱執行緒不安全。

執行緒安全

上面我們分析了導致執行緒不安全出現的原因?那怎麼解決呢?

  • 我們希望一個執行緒操作共享資料結束以後,其他的執行緒才有機會參與共享資料的操作。

執行緒安全是多執行緒程式設計時的計算機程式程式碼中的一個概念。在擁有共享資料的多條執行緒並行執行的程式中,執行緒安全的程式碼會通過同步機制保證各個執行緒都可以正常且正確的執行,不會出現資料汙染等意外情況。

用java程式碼來實現,主要有兩種方法:

執行緒的同步機制

方法一 :同步程式碼塊

格式如下 使用synchronized關鍵字

synchronized(同步監視器){
    //需要被同步的程式碼塊(操作共享資料的程式碼塊)
}
複製程式碼

以上同步監視器 又稱為“鎖”,鎖需要唯一,程式碼如下

class Window implements Runnable{//實現介面
    int ticket=100;
    @Override
    public void run() {
        while (true){
            synchronized (this){
                if(ticket>0){
                    try {
                        Thread.currentThread().sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"售票:票號為:"+ ticket--);
                }
            }
        }
    }
}
複製程式碼

執行後如下,正常!

Java基礎之執行緒安全

方法二 :同步方法

將操作共享資料的程式碼 提取到一個方法內 然後用synchronized 修飾

synchronized void show(){
    //操作共享資料的程式碼
}
複製程式碼

修改賣票程式用同步方法實現如下:

class Window implements Runnable{//實現介面
    int ticket=100;
    public synchronized void show(){
        if(ticket>0){
            try {
                Thread.currentThread().sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"售票:票號為:"+ ticket--);
        }
    }
    @Override
    public  void run() {
        while (true){
            this.show();
        }
    }
}

複製程式碼

注意在同步方法實現中 鎖預設為this 也需要唯一, 我們用圖解釋下執行緒安全下兩個執行緒如何操作共享資料的:

Java基礎之執行緒安全

由上圖我們知道,一旦遇到操作共享資料時,執行緒總是同步執行的。

總結

  • 遇到多個執行緒操作共享資料時就會出現執行緒不安全問題
  • 同步方法或者同步程式碼塊 都是為了讓執行緒同步執行
  • 同步方法和同步程式碼塊都需要一個物件 作為鎖,這個鎖要確保唯一性

喜歡本文的朋友們,歡迎長按下圖關注訂閱號"我的程式設計筆記",收看更多精彩內容~~

Java基礎之執行緒安全

相關文章