一文看盡Java-多線程概念

1、前言程序員

    主要講解一下多線程中的一些概念,本文以後就開始針對JUC包的設計開始解讀;編程

2、概念緩存

    線程安全安全

    1.存在共享數據(臨界資源);2.多個線程同時操做共享數據;只有同時出現這兩種狀況的時候纔會形成線程安全問題;多線程

    解決線程安全併發

    同一時刻有且只有一個線程在操做共享數據,其餘線程必須等到該線程處理完數據之後在對共享數據進行操做;app

    多線程特性編程語言

    原子性ide

    如今的操做系統主要是經過時間分片的形式來管理線程或者進程,Java編程語言一句語言須要多條CPU指令來完成,Java在多線程切換的時候因爲不知足原子性的特徵,致使共享變量產生意料以外的結果;典型的count+=1,以下圖,共享變量的count的指最終結果是1而不是2;函數

   

    可見性

    在多處理器(CPU)系統中,每一個處理器都有本身的高速緩存,而它們又共享同一主內存,總體結構以下圖:

    

    在單核CPU的狀況下,CPU緩存與內存數據一致性問題容易處理,由於全部線程的操做都是針對同一個CPU的緩存,一個線程對緩存的寫對於另外的線程必定是可見的,總體的執行狀況以下圖:

    

    在多CPU的狀況下,每一個CPU都有本身的緩存,這個時候每一個共享變量在CPU中的緩存都是不可見的,這個時候就產生了CPU緩存與內存數據一致性的問題,總體執行的狀況以下圖,因爲count變量分別在不一樣的CPU上執行,相互看不到對方的操做,這個時候變量count就會不一致,產生意料以外的結果,針對這種我也寫了一個demo;

   

/**
 * @author wtz
 *
 * 線程可見性demo
 */
public class ThreadVisiable {

    private int count = 0;

    private void add() {
        int retry = 0;
        while (retry < 10000) {
            count += 1;
            retry++;
        }
    }

    public int sumCount() throws InterruptedException {

        Thread thread1 = new Thread(() -> {
            add();
        });

        Thread thread2 = new Thread(() -> {
            add();
        });

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        return count;
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadVisiable threadVisiable = new ThreadVisiable();
        int count = threadVisiable.sumCount();
        System.out.println(count);
    }

}
View Code

    有序性

    編譯器爲了優化性能,有的時候會改變程序中語句的執行順序,在Java經典的雙重檢查建立單例模式,就是其中的一個體現,代碼以下圖:

/**
 * @author wtz
 * <p>
 * 雙重鎖定單例模式 指令重排
 */
public class Singleton {

    private static Singleton singleton;

    private Singleton() {
    }

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

}
View Code

    代碼總體上看起來無任何瑕疵,可是實際這個方法並不完美,問題出在new的操做上,正常狀況下new Singleton()執行的操做以下步驟:

    1.在內存中分配一塊空間;

    2.在內存上初始化Singleton對象;

    3.將內存地址賦值給singleton變量;

    通過編譯器優化之後多是這個樣子:  

    1.在內存中分配一塊空間;

    2.將內存地址賦值給singleton變量;

    3.在內存上初始化Singleton對象; 

    優化之後當CPU時間片切換時間恰好是線程B判斷爲空的時候,這個時候singleton此時不爲空,不須要進入鎖中,這個時候就返回爲初始化的singleton,總體性執行過程以下圖:

   

     

    競態條件&臨界區

    當兩個線程競爭同一資源時,若是對資源的訪問順序敏感,就稱存在競態條件。致使競態條件發生的代碼區稱做臨界區;

    互斥鎖

    互斥鎖解決了併發程序中的原子性問題,保證同一時刻只有一個線程執行,保證了一個或多個操做在CPU執行的過程當中不被中斷,Java原生語言主要是經過synchronized實現互斥鎖;

    Java內存模型 

    Java內存模型主要是爲了解決內存可見性的問題,使用內存模型約束了CPU緩存和編譯優化;Java內存模型(JMM)從不一樣的角度來講均可以說不少的東西,好比從線程角度來講,JMM規範不一樣線程之間線程通訊的問題,從操做系統的角度來講,JMM規範了工做線程與內存之間訪問的問題;咱們主要從程序員角度來看這個問題的話我認爲能夠從三方面提及:

    1.volatile、synchronized和final語義;

    2.JUC併發包;

    3.happens-before;

    咱們主要說第3點,第1,2點之後在補充,咱們先要明白一些概念,才能更好的理解後面的一些內容;happens-before主要有8個原則,咱們通俗的話來說講:

    1.程序的順序性,單線程的每一個前面的操做優先於後面的操做;

    2.volatile,對於volatile修飾的變量,寫的操做必定優先於讀的操做,也就是說對變量寫操做對於後續的讀操做都是可見的;

    3.鎖,解鎖的操做優先於加鎖的操做,在Java鎖指的就是synchronized,變量在解鎖以前的操做,在從新加鎖以後必定能夠看到;

    4.傳遞性,A優先於B,B優先於C,則A優先於C;

    5.線程開始原則,主線程A啓動子線程B,則子線程B可以看到主線程A在啓動B子線程以前的操做;

    6.線程終止原則,主線程A等待子線程B完成,當子線程B完成之後,主線程A可以看到子線程的B的操做;

    7.線程中斷原則,對線程interrupt()方法優先於發生被中斷線程檢測到中斷事件的發生;

    8.對象終結規則,構造函數的執行必定優先於它的finalize方法;

    等待-通知機制

    等待-通知機制主要是爲了處理循環等待形成的CPU消耗問題,主要有如下兩個步驟:

    1.線程首先獲取互斥鎖,當線程要求的條件不知足時,釋放互斥鎖,進入等待狀態;

    2.當要求的條件知足時,通知等待的線程,從新獲取互斥鎖;

    Java原生語言主要是經過synchronized + wait + notify/notifyAll實現;

    活躍性

    死鎖

    死鎖的定義一組相互競爭資源的線程因相互等待,致使永久阻塞的現象,發生死鎖必備的四個條件:

    1.互斥,共享資源同時只有佔用一個線程;

    2.佔有且等待,線程A獲取共享資源X,在等待共享資源Y的時候,不是釋放共享資源Y;

    3.不可搶佔,其餘線程不能搶佔線程A獲取的共享資源;

    4.循環等待,線程A等待線程B獲取的共享資源,線程B等待線程A獲取的共享資源;

    只要破壞其中任意一個條件就能夠跑壞死鎖;

    活鎖

    線程之間互相謙讓,致使線程沒法執行下去,解決方案經過給線程隨機等待一個時間;

    飢餓

    線程不能正常的訪問共享資源,而且沒法執行下去,解決線程飢餓的辦法:

    1.保證資源的公平性,也就線程的優先級同樣;

    2.保證資源充足;

    3.避免線程長時間佔用鎖執行;

 3、結束

  歡迎你們加羣438836709!歡迎你們關注我!

     

相關文章
相關標籤/搜索