1. 程式人生 > >1.多執行緒-瞭解多執行緒與高併發

1.多執行緒-瞭解多執行緒與高併發

併發與並行的區別:

併發:兩個任務或者多個任務執行,多個任務交替執行
並行:兩個任務或者多個任務一起同時執行
例子:

     一個CPU,去執行一個多執行緒任務。是不可能並行的,一個CPU只能執行一條命令,CPU會高速的切換執行緒任務去執行。這種情況下執行緒是併發的。
一個系統中擁有多個CPU,執行多執行緒任務,多個CPU會同時執行任務,這種情況是並行。並行也只可能出現在多核CPU中。
兩者雖然本質不同,但是造成的最終效果是一樣的。沒有太必要做詳細的區分。

臨界區:

     臨界區用來表示一個共享資源,可以被多個執行緒使用,但是每一次,只能有一個執行緒去使用。相等於廁所的坑。

阻塞非阻塞:

     假如有兩個執行緒,把他們當作兩個人,都拉肚子,只有一個廁所,兩人都必須要上這個廁所。

阻塞:

     一個人先進去了,另外一個人在等待,另外一個人必須要等第一個人出來,才能進去搞事情,這個就是阻塞行為。

非阻塞:

     強調沒有一個執行緒可以妨礙其他執行緒去執行任務。所有執行緒都會嘗試不斷前向執行。後面會有更詳細的描述。

死鎖,飢餓,活鎖

死鎖:

     一個單行道,有一個車,正在往前駕駛,然後有一個車從反方向駕駛過來,這兩個誰都不想退,那麼這個狀態將會一直這樣維持下去。執行緒如果發生這種類似的行為,那麼就可能會造成死鎖。

飢餓:

     某個執行緒或者多個執行緒因為某些原因無法獲得所需要的資源,比如有一個特大的餅,有很多人都想要吃,武力值高的肯定先吃,但是武力值高的人太多,導致那些武力值極低的人,會一直吃不到,這個就像等於執行緒中的優先順序,優先順序高的優先去做某事。跟死鎖相比,飢餓還是會在某一段時間解決的,比如武力值高的都吃餅吃飽了,就到武力值低的去吃了。

活鎖:

     過馬路的時候可能碰到這種情況,你往前走,有一個人騎著自行車往你這個方向駛來,他看到你了,你看到他了,你想讓他,他想讓你,你往左,他往右,你往右,他往左,兩個一直保持著禮讓的態度,就會一直這樣來回重複做這樣的事情。現實中還好說,人可以去交流,幾次過後就解決了,但是對於執行緒,如果沒有給執行緒賦予這種處理的思路,它就可能一直重複和另一個執行緒做這種“禮讓”的事情,導致沒有一個執行緒可以同時拿到所有資源去正常執行任務。

併發級別:

     由於臨界區的存在,我們必須控制多執行緒間的併發,根據控制併發的測率,我們可以把併發的級別進行分類,大致上可以分為:阻塞~無飢餓~無障礙~無鎖~無等待五種。

阻塞:

     java中的synchronized關鍵字,都會試圖去得到臨界區的鎖,如果得不到,執行緒就會被掛機等待,知道佔有了資源為止。是一種悲觀策略,認為肯定會有執行緒去搶資源,一個執行緒搶到資源,其他的就會掛起等待執行完畢,再去試探。

無飢餓:

     公平鎖,不管優先順序多高,講究的是先來後到,這樣所有的執行緒都有機會去執行。

無障礙:

     是一種樂觀策略,認為不會有執行緒去搶資源,無障礙的去執行,如果檢測到衝突,就回滾。

無鎖:

     保證有一個執行緒能在有限步內完成操作離開臨界區。

無等待:

     要求所有執行緒必須在有限步內完成操作。

執行緒的三個特性:

原子性:

     指的是一個操作不可中斷,哪怕多個執行緒同時操作一個變數,每個執行緒只改它要改的值,不管被其它執行緒修改成什麼值。

可見性:

     並行多執行緒修改了某一個共享變數的值,其他執行緒並不一定能夠立即知道這個修改。

有序性:

     程式在執行的時候,程式的程式碼執行順序和語句的順序是一致的,在Java記憶體模型中,允許編譯器和處理器對指令進行重排序,但是重排序過程不會影響到序列程式的執行,卻會影響到多執行緒併發執行的正確性。

指令重排:
 1/**
2 * @program: test
3 * @description: 
4 * @author: Mr.Yang
5 * @create: 2018-11-06 11:22
6 **/
7public class A {
8    int a=0;
9    boolean flag=false;
10    public void  write(){
11        a=1;
12        flag=true;
13    }
14    public void reader(){
15        if(flag){
16            int i =a+1;
17        }
18    }
19}

     假設執行緒A先執行writer方法,接著執行緒B執行reader()方法,如果發生指令重排,執行緒B在第11行程式碼,不一定能看到a被賦值1了。

指令重排的原因:

     提高效能。可以共同執行任務,不需要等待第一個執行完成之後再去執行另一個任務。如果做成要任務執行完成再去執行另外一個,那麼就和序列無太大區別了,效能也會嚴重受損。

一些指令是不能重排的:Happen-Before規則
程式順序原則:
  • 一個執行緒內保證程式碼正常順序執行

  • volatile規則:volatile變數的寫,先發生與讀,保證了volatile的可見性

  • 鎖規則:解鎖必然發生在隨後的加鎖前

  • 傳遞性:A引用B,B引用C,那麼C必然先在A前執行。

  • 執行緒的start()方法先於它的每一個動作

  • 執行緒的所有操作先於執行緒的完結

  • 執行緒的中斷先於被中斷執行緒的程式碼

  • 物件的建構函式執行和結束先於finalize()方法