1. 程式人生 > 程式設計 >JAVA多執行緒知識彙總

JAVA多執行緒知識彙總

執行緒概念

程序:啟動一個應用程式就叫一個程序。 接著又啟動一個應用程式,這叫兩個程序。每個程序都有一個獨立的記憶體空間;程序也是程式的一次執行過程,是系統執行程式的基本單位;系統執行一個程式即是一個程序從建立、執行到消亡的過程。

執行緒:執行緒是在程序內部同時做的事情,一個程序中可以有多個執行緒,這個應用程式也可以稱之為多執行緒程式。

一個程式執行後至少有一個程序,一個程序中可以包含多個執行緒

執行緒排程:

  • 分時排程:所有執行緒輪流使用 CPU 的使用權,平均分配每個執行緒佔用 CPU 的時間。
  • 搶佔式排程:優先讓優先順序高的執行緒使用 CPU,如果執行緒的優先順序相同,那麼會隨機選擇一個(執行緒隨機性),Java使用的為搶佔式排程。

建立多執行緒

方法一:建立Thread類的子類

  • 建立Thread類的子類,並重寫該類的run()方法,設定執行緒任務。
  • 建立Thread子類的例項,即建立了執行緒物件
  • 呼叫執行緒物件的start()方法來啟動該執行緒
//方法一:
//定義Thread類的子類,並重寫該類的run()方法
public class MyThreadDemo01 extends Thread {
  @Override
  public void run() {
    for (int i = 0; i < 20 ; i++) {
      System.out.println(getName()+"-->"+i);
    }
  }
}
//主執行緒
public class MainThread01 {
  public static void main(String[] args) {
    //建立Thread子類的例項,即建立了執行緒物件
    MyThreadDemo01 thread01 = new MyThreadDemo01();

    //呼叫執行緒物件的start()方法來啟動該執行緒
    thread01.start();

    for (int i = 0; i < 10 ; i++) {
      System.out.println(Thread.currentThread().getName()+"-->"+i);
    }
  }
}

public static Thread currentThread() :返回對當前正在執行的執行緒物件的引用。

public String getName() :獲取當前執行緒名稱。

public void start() :導致此執行緒開始執行; Java虛擬機器呼叫此執行緒的run方法。

public void run() :此執行緒要執行的任務在此處定義程式碼。

public static void sleep(long millis) :使當前正在執行的執行緒以指定的毫秒數暫停(暫時停止執行)。

方法二:實現Runnable介面

  • 定義Runnable介面的實現類,並重寫該介面的run()方法,設定執行緒任務
  • 建立Runnable實現類物件
  • 建立Thread類的物件,並且該物件構造方法中傳遞Runnable實現類物件
  • 呼叫Thread物件的start()方法來啟動執行緒
//方法二:
//定義Runnable介面的實現類,並重寫該介面的run()方法,設定執行緒任務
public class MyThreadDemo02 implements Runnable{
  @Override
  public void run() {
    for (int i = 0; i < 10 ; i++) {
      System.out.println(Thread.currentThread().getName()+"-->"+i);
    }
  }
}
 //主執行緒
public class MainThread {
  public static void main(String[] args) {  
    //建立Runnable實現類物件
    MyThreadDemo02 runnable = new MyThreadDemo02();

    //建立Thread類的物件,並且該物件構造方法中傳遞Runnable實現類物件
    Thread thread02 = new Thread(runnable);

    //呼叫Thread物件的start()方法來啟動執行緒
    thread02.start();

    for (int i = 0; i < 20 ; i++) {
      System.out.println(Thread.currentThread().getName()+"-->"+i);
    }
  }
}

方法三:匿名內部類方式

  • 匿名內部類能夠簡化程式
//方法三:匿名內部類
public class MainThread {
  public static void main(String[] args) {
    //Thread方式
    new Thread(){
      @Override
      public void run() {
        for (int i = 0; i < 10 ; i++) {
          System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
      }
    }.start();
    //Runnable介面方式
	 new Thread(new Runnable() {
      @Override
      public void run() {
        for (int i = 0; i < 10 ; i++) {
          System.out.println(Thread.currentThread().getName()+"-->"+i);
        }
      }
    }).start();
    ////////////////////////////////////////////////

    for (int i = 0; i < 20 ; i++) {
      System.out.println(Thread.currentThread().getName()+"-->"+i);
    }
  }
}

執行緒安全問題

多執行緒訪問共享資料,,且多個執行緒中對資源有寫的操作,就會出現執行緒安全問題

執行緒安全問題都是由全域性變數及靜態變數引起的。若每個執行緒中對全域性變數、靜態變數只有讀操作,而無寫操作,一般來說,這個全域性變數是執行緒安全的;若有多個執行緒同時執行寫操作,一般都需要考慮執行緒同步, 否則的話就可能影響執行緒安全。

解決執行緒安全問題採用執行緒同步機制,主要有以下三種方式:

  • 同步程式碼塊
  • 同步方法
  • 鎖機制

同步程式碼塊

同步程式碼塊:synchronized 關鍵字可以用於方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。

  • 格式:synchronized(鎖物件){ //訪問共享資料的程式碼 }
  • 鎖物件可以是任意型別
  • 多個執行緒物件要使用同一把鎖
  • 鎖物件是將同步程式碼塊鎖住,只讓執行緒在同步程式碼塊中執行
public class SafeRunnableDemo implements Runnable {
  private int ticket = 100;

  //同步程式碼塊
  //建立鎖物件
  Object lock = new Object();

  @Override
  public void run() {
    while (true){
      //鎖住同步程式碼塊
      synchronized (lock){
        if (ticket > 0) {
          System.out.println(Thread.currentThread().getName() + "正在賣第" + ticket + "張");
          ticket--;
        }
      }
    }
  }
}

同步方法

同步方法:使用synchronized修飾的方法,就叫做同步方法,保證A執行緒執行該方法的時候,其他執行緒只能在方法外等著

  • 格式:修飾符 synchronized 返回值型別 方法名(引數列表) { //訪問共享資料的程式碼 }
  • 把共享了同步資料的程式碼抽取出來,放入同步方法中
public class SafeRunnableDemo implements Runnable {
  private int ticket = 100;

  //同步方法
  //定義一個同步方法
  public synchronized void lock(){
    //同步方法鎖住程式碼塊
    if (ticket > 0) {
      System.out.println(Thread.currentThread().getName() + "正在賣第" + ticket + "張");
      ticket--;
    }
  }

  //重寫run並使用同步方法
  @Override
  public void run() {
    while (true){
      lock();
    }
  }
}

Lock鎖

Lock提供了比synchronized更廣泛的鎖操作

  • 在Lock介面中 void lock() 獲取鎖,void unlock() 釋放鎖
  • 需要在成員位置處建立ReentraLock物件,在共享資料程式碼塊之前呼叫方法lock()獲取鎖,在之後用unlock()方法釋放鎖
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SafeRunnableDemo implements Runnable {
  private int ticket = 100;

  //Lock鎖方法
  //建立ReentrantLock物件
  Lock lock = new ReentrantLock();
  @Override
  public void run() {
    while (true){
      //在可能出現問題的程式碼塊之前用lock()方法
      lock.lock();
      if (ticket > 0) {
        System.out.println(Thread.currentThread().getName() + "正在賣第" + ticket + "張");
        ticket--;
      }
      //在可能出現問題的程式碼塊之後用unlock()方法
      lock.unlock();
    }
  }
}

執行緒機制

  • NEW(新建):執行緒剛被建立,但是並未啟動。還沒呼叫start()方法。
  • Runnable(可執行):執行緒可以在java虛擬機器中執行的狀態,可能正在執行自己程式碼,也可能沒有,這取決於作業系統處理器。
  • Blocked(鎖阻塞):當一個執行緒試圖獲取一個物件鎖,而該物件鎖被其他的執行緒持有,則該執行緒進入Blocked狀態;當該執行緒持有鎖時,該執行緒將變成Runnable狀態。
  • Waiting(無限等待):一個執行緒在等待另一個執行緒執行一個(喚醒)動作時,該執行緒進入Waiting狀態。進入這個狀態後是不能自動喚醒的,必須等待另一個執行緒呼叫notify()或者notifyAll()方法才能夠喚醒。
  • Timed Waiting(計時等待):同waiting狀態,有幾個方法有超時引數,呼叫他們將進入Timed Waiting狀態。這一狀態 將一直保持到超時期滿或者接收到喚醒通知。帶有超時引數的常用方法有Thread.sleep()、Object.wait()。
  • Teminated(被終止):因為run方法正常退出而死亡,或者因為沒有捕獲的異常終止了run方法而死亡。

一個呼叫了某個物件的 Object.wait() 方法的執行緒會等待另一個執行緒呼叫此物件Object.notify()方法 或 Object.notifyAll()方法。

其實waiting狀態並不是一個執行緒的操作,它體現的是多個執行緒間的通訊,可以理解為多個執行緒之間的協作關係, 多個執行緒會爭取鎖,同時相互之間又存在協作關係。

當多個執行緒協作時,比如A,B執行緒,如果A執行緒在Runnable(可執行)狀態中呼叫了wait()方法那麼A執行緒就進入 了Waiting(無限等待)狀態,同時失去了同步鎖。假如這個時候B執行緒獲取到了同步鎖,在執行狀態中呼叫了 notify()方法,那麼就會將無限等待的A執行緒喚醒。注意是喚醒,如果獲取到鎖物件,那麼A執行緒喚醒後就進入 Runnable(可執行)狀態;如果沒有獲取鎖物件,那麼就進入到Blocked(鎖阻塞狀態)。

public class WaitAndSleep {
  public static void main(String[] args) {
    //建立鎖物件
    Object lock = new Object();

    //匿名內部類建立執行緒1
    new Thread(){
      @Override
      public void run() {
        System.out.println(Thread.currentThread().getName()+"需要買票");
        //用同步程式碼塊包裹
        synchronized (lock){
          try {
            //lock.wait(5000);//到5秒自動醒來
            lock.wait();//進入無限等待,需要喚醒
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
        System.out.println(Thread.currentThread().getName()+"買到了票");
      }
    }.start();

    //匿名內部類建立執行緒2
    new Thread(){
      @Override
      public void run() {
        try {
          Thread.sleep(5000);//等待5秒
          System.out.println(Thread.currentThread().getName()+"出票了");
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        //用同步程式碼塊包裹
        synchronized (lock){
          lock.notify();//如果有多個執行緒等待,隨機喚醒一個
          //lock.notifyAll();//喚醒所有等待的執行緒
        }
      }
    }.start();
  }
}

執行緒池

當在系統中用到了很多的執行緒,大量的啟動和結束動作會導致系統的效能變卡,響應變慢,採用執行緒池可以解決這個問題。執行緒池就相當於一個容器(如同ArrayList),執行的任務放入執行緒池中,多出來的任務就等待執行緒池中的任務執行完再放入。

  • 使用執行緒池的工廠類 Executors 裡的靜態方法 newFixedThreadPool 生產指定執行緒數量的執行緒池,返回為ExecutorService介面
  • 建立一個類實現Runnable介面,重寫run方法,設定執行緒任務
  • 呼叫ExecutorService中的submit方法,傳遞執行緒任務,開啟執行緒
  • 銷燬執行緒池:ExecutorService中的shutdown方法
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//執行緒池
public class ThreadPoolMain {
  public static void main(String[] args) {
    //使用執行緒池的工廠類 Executors裡的靜態方法 newFixedThreadPool
    // 生產指定執行緒數量的執行緒池,返回為ExecutorService介面
    ExecutorService es = Executors.newFixedThreadPool(2);

    //呼叫ExecutorService中的submit方法,傳遞執行緒任務,開啟執行緒
    es.submit(new ThreadPoolDemo01());
  }
}

//////////////////////////////////////////////////////

//建立一個類實現Runnable介面,重寫run方法,設定執行緒任務
public class ThreadPoolDemo01 implements Runnable{
  @Override
  public void run() {
    ...
  }
}

以上就是JAVA多執行緒知識彙總的詳細內容,更多關於JAVA多執行緒的資料請關注我們其它相關文章!