5分鐘搞懂多執行緒安全問題

維C發表於2020-08-04

執行緒相關的面試題經久不衰,尤其是在JAVA領域

曾經某架構師對我說,不懂執行緒的是初級,懂執行緒的是高階,半懂不懂的是中級。

可見執行緒在面試中是一個怎樣的角色了。但是,面試造火箭,入職擰螺絲是常態。我們真的有必要學執行緒嗎?

有,很有必要!

就算是擰螺絲,你拿著比別人多的薪水擰,難道不舒服嗎?

什麼是執行緒?

你就是一個執行緒

或者說,你擰螺絲的時候真像一個執行緒.

執行緒在計算機中是CPU的最小排程單位,啥意思呢?

程式要執行就會在記憶體中佔用記憶體,那麼計算機要怎麼給程式分配記憶體呢,這個就由作業系統來管理分配,作業系統要怎麼統一分配呢?

這時就產生了程式的概念。

作業系統會為每一個程式分配一塊地盤,並加上標識,記錄這是哪個程式的地盤。

地盤分完了得幹活呀。誰來幹呢?執行緒就出馬了,每個程式都會有一個執行緒在工作。

而且一個程式可以有N個執行緒存在.

但同一時間,只能執行一個執行緒的工作(單核CPU中)。

這個就是所謂的最小排程單位,也可以說是最小工作單位。

所以說,你在擰螺絲的時候就是個執行緒。而在公司裡有千千萬萬個這樣的執行緒。

假設,你接到一個需求,要擰200個螺絲,你一看文件現在還有200個整沒擰。 這時候你的同事也接到這個需求,一看文件剩200個沒擰。 這時候你們都去擰了一個,各自記錄-1,還剩199個沒擰。 但其實已經擰了2個了,這就有問題了。

用程式碼來演示一下,300個人擰200個螺絲會出現什麼情況。


package Thread;

public class MyRun implements Runnable {

    public static int luosi = 200;
    @Override
    public  void run() {
        for (int i = 1;i<=100;i++) {   // 100人去擰這200個螺絲
            if (luosi > 0) {
                try {
                    Thread.sleep(50);   // 假設每個人耗費50毫秒去擰
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                luosi-= 1;  // 擰成功了,總數減去1
                System.out.println(Thread.currentThread()+"擰了螺絲,還剩:"+luosi+"個沒擰");
            }
        }
    }
}

再來個入口函式去執行:


package Thread;

public class ThreadTest {

    public static void main(String[] args) throws InterruptedException {
        MyRun mr = new MyRun();

        Thread mh = new Thread(mr);   // 第一個地方 100個人擰
        Thread mh2 = new Thread(mr);  // 第二個地方
        Thread mh3 = new Thread(mr);  // 第三個地方
        mh.start();
        mh2.start();
        mh3.start();
    }

}

結果:

Thread[Thread-1,5,main]擰了螺絲,還剩:2個沒擰
Thread[Thread-0,5,main]擰了螺絲,還剩:2個沒擰
Thread[Thread-1,5,main]擰了螺絲,還剩:0個沒擰
Thread[Thread-2,5,main]擰了螺絲,還剩:-1個沒擰
Thread[Thread-0,5,main]擰了螺絲,還剩:0個沒擰

再來看下總共擰了多少螺絲

可以看到擰了222次螺絲。

這個就是傳說中的執行緒安全問題。

多個執行緒操作同一個資料,出現的資料紊亂現象

回想一下,在擰螺絲的時候,是否都要在擰之前檢視一下還有多少個螺絲沒擰.

因為每個人都有一份文件,各自優先更新自己的那份,沒有及時同步給其他人.

這個例子和JAVA工作模式很是相近,畫個圖讓大家理解一下:

每個執行緒都是優先操作自己的工作區,而主記憶體更新有可能不及時。

最經典的一個辦法

  • 加鎖

保證同一時間只有一個人在操作,並且直接更新主記憶體資料,拿到鎖的一方也必須從主記憶體讀取最新資料進行操作.

JAVA如何解決:

  • synchronized

package Thread;

public class MyRun implements Runnable {

    public static int luosi = 200;
    @Override
    public  void run() {
        for (int i = 1;i<=100;i++) {   // 100人去擰這200個螺絲
            synchronized (MyRun.class) {  // 加鎖,保證原子性,可見性操作
                if (luosi > 0) {
                    try {
                        Thread.sleep(50);   // 假設每個人耗費50毫秒去擰
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    luosi-= 1;  // 擰成功了,總數減去1
                    System.out.println(Thread.currentThread()+"擰了螺絲,還剩:"+luosi+"個沒擰");
                }
            }

        }
    }
}

結果:

Thread[Thread-0,5,main]擰了螺絲,還剩:4個沒擰
Thread[Thread-0,5,main]擰了螺絲,還剩:3個沒擰
Thread[Thread-2,5,main]擰了螺絲,還剩:2個沒擰
Thread[Thread-1,5,main]擰了螺絲,還剩:1個沒擰
Thread[Thread-1,5,main]擰了螺絲,還剩:0個沒擰

擰了多少:

這次正常了,我們完美的解決了執行緒安全問題。

執行緒安全問題只會出現在多個執行緒操作同一個資料上,否則不會出現執行緒安全問題。 而一般解決這種問題的方式就是加鎖。我們回想一下,這個鎖是不是很熟悉,在MySQL中,多個事務操作同一條資料也是通過加鎖來隔離的。而這都會造成一個共同的問題,效能下降,甚至死鎖問題。

加鎖是否是解決執行緒安全問題的最優解呢?

而且我們在做業務需求時,真的有必要開啟多執行緒嗎?我覺得這200個螺絲,我一個人擰的更快!

PS:你的贊是我創作的動力!

為什麼同是9年義務教育別人就那麼優秀?

想知道請關注訂閱號:多邊形戰士(關注送 laravel,linux,nginx 等學習資料!!!)

回覆’學習’,推薦你2本書。

回覆’資源’,獲取我收集到的一些優質資源。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章