1. 程式人生 > >sincerit java基礎之多執行緒

sincerit java基礎之多執行緒

執行緒狀態
在這裡插入圖片描述
Java執行緒具有五中基本狀態

新建狀態(New):當執行緒物件對建立後,即進入了新建狀態,如:Thread t = new MyThread();

就緒狀態(Runnable):當呼叫執行緒物件的start()方法(t.start();),執行緒即進入就緒狀態。處於就緒狀態的執行緒,只是說明此執行緒已經做好了準備,隨時等待CPU排程執行,並不是說執行了t.start()此執行緒立即就會執行;

執行狀態(Running):當CPU開始排程處於就緒狀態的執行緒時,此時執行緒才得以真正執行,即進入到執行狀態。注:就 緒狀態是進入到執行狀態的唯一入口,也就是說,執行緒要想進入執行狀態執行,首先必須處於就緒狀態中;

阻塞狀態(Blocked):處於執行狀態中的執行緒由於某種原因,暫時放棄對CPU的使用權,停止執行,此時進入阻塞狀態,直到其進入到就緒狀態,才 有機會再次被CPU呼叫以進入到執行狀態。根據阻塞產生的原因不同,阻塞狀態又可以分為三種:

1.等待阻塞:執行狀態中的執行緒執行wait()方法,使本執行緒進入到等待阻塞狀態;

2.同步阻塞 – 執行緒在獲取synchronized同步鎖失敗(因為鎖被其它執行緒所佔用),它會進入同步阻塞狀態;

3.其他阻塞 – 通過呼叫執行緒的sleep()或join()或發出了I/O請求時,執行緒會進入到阻塞狀態。當sleep()狀態超時、join()等待執行緒終止或者超時、或者I/O處理完畢時,執行緒重新轉入就緒狀態。

死亡狀態(Dead):執行緒執行完了或者因異常退出了run()方法,該執行緒結束生命週期

Runnable狀態是執行緒獲得cpu資源的唯一入口,緒狀態是進入到執行狀態的唯一入口,也就是說,執行緒要想進入執行狀態執行,首先必須處於就緒狀態中;所以獲得synchronized之後是先到就緒狀態再到執行狀態

如何使用多執行緒
第一個是自己定義一個類MyThread在繼承Thread,再重寫run方法

class MyThread extends Thread {
    private int i = 0;
    @Override
    public void run() {
        // TODO Something
} }

第二個是實現Runnable介面,並重寫該介面的run()方法

class MyRunnable implements Runnable {
    private int i = 0;
    @Override
    public void run() {
        // TODO something
    }
}

再建立一個執行緒物件,啟動執行緒到就緒狀態

1. MyThread myThread = new MyThread();
2. myThread.start();
 
1. MyRunnable myRunnable = new MyRunnable();
2. myRunnable.strat();

或者在主函式直接建立Thread物件實現重寫run()在啟動執行緒  
new Thread(new Runnable(){
  public void run() {
    // TODO something 
  }
}).start();

使用多執行緒就不得不瞭解執行緒同步的概念

什麼是執行緒同步
執行緒同步,可理解為程序或執行緒A和B一塊配合,A執行到一定程度時要依靠B的某個結果,於是停下來,示意B執行;B依言執行,再將結果給A;A再繼續操作。A,B相互協調完成任務

為何要使用執行緒同步機制(synchronized)
java允許多執行緒併發控制,當多個執行緒同時操作一個可共享的資源變數時(如資料的增刪改查), 多個執行緒讀或者寫相同的資料等情況時可能會導致資料不一致。為了解決這些問題,引入了synchronized同步方法

使用synchronized實現同步方法背後的原理:
即有synchronized關鍵字修飾的方法,這個方法肯定是某一個類裡面的,要用物件呼叫
由於java的每個物件都有一個內建鎖,當用此關鍵字修飾方法時,
內建鎖會保護整個方法。在呼叫該方法前,需要獲得內建鎖,否則就處於阻塞狀態。

所以要使用公共資源變數去操作資料時一定要先拿到內建鎖,否則只能等待其他執行緒執行完釋放鎖才能去獲取鎖
注: synchronized關鍵字也可以修飾靜態方法,此時如果呼叫該靜態方法,將會鎖住整個類

synchronized可以有三種加鎖的方式
成員方法程式碼塊上加鎖

public void method() {
  synchronized(this) { // this 指的是這個物件
    // TODO 程式碼塊
  }
}

成員方法上加鎖

public synchronized void method() {
     // TODO 程式碼塊
  }

靜態方法加鎖

public class SynchronizedTest {
  // 取得的鎖很特別,是當前呼叫這個方法的物件所屬的類(Class,而不再是由這個Class產生的某個具體物件了)。
      public static synchronized void method1(){ 
          System.out.println("Method 1 start");
          try {
              System.out.println("Method 1 execute");
              Thread.sleep(3000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          System.out.println("Method 1 end");
      }
 
      public static synchronized void method2(){
          System.out.println("Method 2 start");
          try {
              System.out.println("Method 2 execute");
              Thread.sleep(1000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          System.out.println("Method 2 end");
      }
 
      public static void main(String[] args) {
          final SynchronizedTest test = new SynchronizedTest(); 
          final SynchronizedTest test2 = new SynchronizedTest();
 
          new Thread(new Runnable() {
              @Override
              public void run() {
                  test.method1();   // 直接呼叫
              }
          }).start();
 
          new Thread(new Runnable() {
              @Override
              public void run() {
                  test2.method2();
              }
          }).start();
      }
  }

使用synchronized的缺陷
出自該片: https://www.cnblogs.com/dolphin0520/p/3923167.html

synchronized是java中的一個關鍵字,也就是說是Java語言內建的特性。那麼為什麼會出現Lock呢(lock是一個介面)?

我們瞭解到如果一個程式碼塊被synchronized修飾了,當一個執行緒獲取了對應的鎖,並執行該程式碼塊時,其他執行緒便只能一直等待,等待獲取鎖的執行緒釋放鎖,而這裡獲取鎖的執行緒釋放鎖只會有兩種情況:

1)獲取鎖的執行緒執行完了該程式碼塊,然後執行緒釋放對鎖的佔有;

2)執行緒執行發生異常,此時JVM會讓執行緒自動釋放鎖。

那麼如果這個獲取鎖的執行緒由於要等待IO或者其他原因(比如呼叫sleep方法)被阻塞了,但是又沒有釋放鎖,其他執行緒便只能乾巴巴地等待,試想一下,這多麼影響程式執行效率。

因此就需要有一種機制可以不讓等待的執行緒一直無期限地等待下去(比如只等待一定的時間或者能夠響應中斷),通過Lock就可以辦到。

再舉個例子:當有多個執行緒讀寫檔案時,讀操作和寫操作會發生衝突現象,寫操作和寫操作會發生衝突現象,但是讀操作和讀操作不會發生衝突現象。

但是採用synchronized關鍵字來實現同步的話,就會導致一個問題:

如果多個執行緒都只是進行讀操作,所以當一個執行緒在進行讀操作時,其他執行緒只能等待無法進行讀操作。

因此就需要一種機制來使得多個執行緒都只是進行讀操作時,執行緒之間不會發生衝突,通過Lock就可以辦到。

另外,通過Lock可以知道執行緒有沒有成功獲取到鎖。這個是synchronized無法辦到的。

總結一下,也就是說Lock提供了比synchronized更多的功能。但是要注意以下幾點:

1)Lock不是Java語言內建的,synchronized是Java語言的關鍵字,因此是內建特性。Lock是一個類,通過這個類可以實現同步訪問;

2)Lock和synchronized有一點非常大的不同,採用synchronized不需要使用者去手動釋放鎖,當synchronized方法或者synchronized程式碼塊執行完之後,系統會自動讓執行緒釋放對鎖的佔用;而Lock則必須要使用者去手動釋放鎖,如果沒有主動釋放鎖,就有可能導致出現死鎖現象。

Lock的用法
看原始碼

public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

常用的是lock() 和 unlock()
lock()方法是平常使用得最多的一個方法,就是用來獲取鎖。如果鎖已被其他執行緒獲取,則進行等待.
如果採用Lock,必須主動去釋放鎖,並且在發生異常時,不會自動釋放鎖。因此一般來說,使用Lock必須在try{}catch{}塊中進行,並且將釋放鎖的操作放在finally塊中進行,以保證鎖一定被被釋放,防止死鎖的發生。通常使用Lock來進行同步的話,是以下面這種形式去使用的:

Lock lock = new ReentrantLock(); // ReentrantLock(重入鎖) 以可以響應中斷的方式加鎖
lock.lock();
try{
    //處理任務
}catch(Exception ex){
     
}finally{
    lock.unlock();   //要手動釋放鎖
}