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); } }
有序性
編譯器爲了優化性能,有的時候會改變程序中語句的執行順序,在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; } }
代碼總體上看起來無任何瑕疵,可是實際這個方法並不完美,問題出在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!歡迎你們關注我!