1. 程式人生 > >Java併發程式設計中的多執行緒是怎麼實現的?

Java併發程式設計中的多執行緒是怎麼實現的?

在這裡插入圖片描述
眾所周知,在Java的知識體系中,併發程式設計是非常重要的一環,也是面試中必問的題,一個好的Java程式設計師是必須對併發程式設計這塊有所瞭解的。
併發必須知道的概念在深入學習併發程式設計之前,我們需要了解幾個基本的概念。同步和非同步同步和非同步用請求返回呼叫的方式來理解相對簡單。
同步:可以理解為發出一個請求後,必須等待返回結果才能執行後續的操作。
非同步:請求發出後,不需要等待返回結果,可以繼續執行後續操作,非同步請求更像是在另一個 “空間” 中處理請求的結果,這個過程不會影響請求方的其他操作。舉個生活中的例子,比如我們去實體店買衣服,挑選完款式後下單讓售貨員去倉庫拿貨,在售貨員拿貨的過程你需要在店裡等待,直到售貨員把衣服交給你後才算購物成功,這就相當於同步的過程。
不過,如果是在網上購物的話,我們只需下單並完成支付,對我們來說整個購物過程就算完成了。網上的商家接到訂單會幫我們加緊安排送貨,這段時間我們可以去做其他的事,比如去外面打個籃球之類的。
等送貨上門並簽收商品就完事了,這個過程就相當於非同步。
併發和並行併發和並行的功能很相似,兩者都可以表示多個任務一起執行的情況,但本質上兩者其實是有區別的。
嚴格意義上來說,並行的多工是真實的同時執行,而併發更多的情況是任務之間交替執行,系統不停的在多個任務間切換執行,也就是 “序列” 執行。
最直接的例子的就是我們的計算機系統,在單核CPU時代,系統表面上能同時進行多工處理,比如聽歌的同時又瀏覽網頁,但真實環境中這些任務不可能是真實並行的,因為一個CPU一次只能執行一條指令,這種情況就是併發,系統看似能處理多工是因為不停的切換任務,但因為時間非常短,所以在我們的感官來說就是同時進行的。
而計算機系統真實的並行是隨著多核CPU的出現才有的。
臨界區臨界區表示公共資源或是共享資料,可以被多個執行緒使用。
但是每次只能有一個執行緒使用它,一旦臨界區的資源被佔用,其他執行緒就必須等到資源釋放後才能繼續使用該資源。
在Java程式開發中,對於這樣的資源一般都需要做同步的操作,例如下面的這段程式碼,用的就是synchronized關鍵字來對臨界區資源進行同步
public class SyncTest implements Runnable {

//臨界區資源
public static SyncTest instance = new SyncTest();
@Override
public void run() {
    synchronized (instance) {
        
    }
}

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(new SyncTest());
    Thread t2 = new Thread(new SyncTest());
    t1.start();
    t2.start();
    t1.join();
    t2.join();
}

}
阻塞和非阻塞阻塞和非阻塞通常用來形容多執行緒間的相互影響。
比如一個執行緒佔用了臨界區的資源,那麼其他需要這個資源的執行緒就必須等待。等待的過程會使執行緒掛起,也就是阻塞。
如果臨界區的資源一直不釋放的話,那麼其他阻塞的執行緒就都不能工作了。
非阻塞則相反,強調的是執行緒之間並不互相妨礙,所有的執行緒都會不斷嘗試向前執行。
死鎖、飢餓和活鎖這三種情況表示的是多執行緒間的活躍狀態,對於執行緒來說,以上的情況都是 “非友好” 的狀態。
1、死鎖一般是指兩個或者兩個以上的執行緒互相持有對方所需的資源,並且永遠在等待對方釋放的一種阻塞狀態。
例如有兩個執行緒A和B同時共享臨界區的資源C,當A佔用C時,B處於阻塞狀態,然而A的釋放需要用到B的資源,這樣一來,就變成了A一直在等待B,B也一直在等待A,互相之間永遠在等待對方釋放的狀態。
一般來說,死鎖的發生是由於程式的設計不合理導致,而且死鎖很難解決,最好的方式就是預防。
2、飢餓是指某一個或者多個執行緒因為種種原因無法獲得所需的資源,導致一直無法執行。比如它的執行緒優先順序太低,而高優先順序的執行緒不斷搶佔它所需的資源,導致低優先順序資源無法工作。
3、活鎖的情況是執行緒一種非常有趣的情況,在生活中我們可能會碰到這樣的情況,那就是出門的時候可能會遇到有人要進門,你打算讓他先進門,他又打算讓你先出門,結果,兩個人都互相退後了,然後你打算先出門時對方也向前一步,來來回回就一直卡在門口。當然,這種事情正常人很快就能解決,但如果是執行緒碰到就沒那麼幸運了。
如果兩個執行緒佔用著公共的資源,並且秉承著 “謙讓” 的原則,主動把資源讓給他人使用,你讓我也讓,這樣就造成資源在兩個執行緒間不斷跳動但執行緒之間都拿不到資源的情況,這樣的情況就是活鎖了。
執行緒安全執行緒安全指的是多執行緒的安全。如果一段程式可以保證被多執行緒訪問後仍能保持正確性,那麼程式就是執行緒安全的。
一般來說,執行緒安全注重的是多執行緒開發中的共享資料的安全。就比如下面這段程式碼:
public class ThreadSafety implements Runnable{

//共享資料
public static int i = 0;
public  void increase(){
    for (int j= 0;j<10; j++){
        i++;
    }
}

@Override
public void run() {
    increase();
}
public static void main(String[] args) throws Exception{
    ThreadSafety demo = new ThreadSafety();
    Thread t1 = new Thread(demo);
    Thread t2 = new Thread(demo);
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(i);
}

}
兩個執行緒 t1 和 t2 同時開啟,執行run方法,在我們的預想中,如果是執行緒安全的話,那麼main的執行結果應該是20,但是因為 i 是共享資料,而程式沒有對 i 的操作做同步的處理,最終執行的結果並不是20,所以這種情況就不是執行緒安全的情況。解決的辦法也比較簡單,可以利用synchronized關鍵字來修飾方法或程式碼塊,這部分的知識也是併發程式設計中非常重要的一塊。
改進後的程式碼如下:
public class ThreadSafety implements Runnable{

//共享資料
public static int i = 0;
public  void increase(){
   synchronized(this){
     for(int j= 0;j<10; j++){
        i++;
      }
  }       }

@Override
public void run() {
    increase();
}
public static void main(String[] args) throws Exception{
    ThreadSafety demo = new ThreadSafety();
    Thread t1 = new Thread(demo);
    Thread t2 = new Thread(demo);
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println(i);
}

}
此時,兩個執行緒 t1 和 t2 同時開啟,執行run方法,資源i就是獨立的了,兩個執行緒會切換執行,當t1執行的時候,會通過synchronized同步鎖將i鎖住,等到t1執行完畢釋放鎖了之後,t2才會執行對i進行操作。
文章來自:https://www.itjmd.com/news/show-5304.html