java多執行緒使用詳解與案例,超詳細

2023-08-23 15:01:47

一、建立執行緒的方式

1、繼承Thread類

  • 讓子類繼承Thread執行緒類
  • 子類必須重寫Thread類的run方法
  • 建立一個自己定義的執行緒物件
  • 呼叫start()方法啟動執行緒
//測試類
/**
 * 1、讓子類繼承Thread執行緒類
 */
public class ThreadTest1 extends Thread {
    //2、子類必須重寫Thread類的run方法

    @Override
    public void run() {
        for (int i = 1;i <=5;i++){
            System.out.println("myThread執行緒輸出" + i);
        }
    }
}
//主執行緒main函數
public class Main {
    //main方法是由一條預設的主執行緒負責執行的
    public static void main(String[] args) {
        //3、建立一個自己定義的執行緒物件
        Thread t = new ThreadTest1();
        //4、啟動執行緒
        //注意是呼叫start方法而不是run方法,呼叫start方法是告訴系統要把t物件單獨開一條執行緒
        //如果呼叫run方法則是呼叫一個普通物件的一個方法,不會另開一條執行緒
        t.start();
        for (int i = 1;i <=5;i++){
            System.out.println("主執行緒main輸出" + i);
        }
    }
}
主執行緒main輸出1
myThread執行緒輸出1
主執行緒main輸出2
myThread執行緒輸出2
主執行緒main輸出3
myThread執行緒輸出3
主執行緒main輸出4
myThread執行緒輸出4
主執行緒main輸出5
myThread執行緒輸出5

程序已結束,退出程式碼0

結果:此時程式有兩條執行緒main和t,這兩條執行緒交替執行。
優點:編碼簡單。
缺點:由於該方式要繼承Thread類,所以就不能繼承其他類,不利於功能擴充套件,不靈活。

2、實現Runnable介面

  • 建立一個任務類實現Runnable方法
  • 實現Runnable介面的run方法
  • 建立一個任務類物件
  • 把任務物件交給Thread執行緒類處理
  • 呼叫執行緒物件start方法啟動執行緒
//任務類
/**
 * 1、定義一個任務類,實現Runnable介面
 */
public class MyRunnable implements Runnable{
    //2、重寫Runnable的run方法
    @Override
    public void run() {
        for (int i = 1;i <=5;i++){
            System.out.println("MyRunnable==》" + i);
        }
    }
}

@Test
    public void RunnableTest(){
        //3、建立任務物件
        //注意這個不是一個執行緒物件,而是任務物件
        Runnable runnable = new MyRunnable();
        //4、把任務物件交給一個執行緒物件處理
        //thread有一個Runnable型別的有參構造:public Thread(Runnable target)
        //thread接受到引數後會自動建立一個匿名執行緒
        new Thread(runnable).start();

        for (int i = 1;i <=5;i++){
            System.out.println("主執行緒main==》" + i);
        }
    }
MyRunnable==》1
MyRunnable==》2
主執行緒main==》1
MyRunnable==》3
主執行緒main==》2
MyRunnable==》4
主執行緒main==》3
MyRunnable==》5
主執行緒main==》4
主執行緒main==》5

程序已結束,退出程式碼0

優點:任務類只是實現介面,還可以繼續繼承其他類,實現其他介面,擴充套件性強。
缺點:需要多個Runnable物件。

擴充套件

由於每次建立執行緒都要建立一個實現介面的類,非常麻煩,可以用匿名內部類或Lambda表示式代替

@Test
    public void RunnableTest2(){
        //匿名內部類寫法1
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i = 1;i <=5;i++){
                    System.out.println("MyRunnable==》" + i);
                }
            }
        };
        new Thread(runnable).start();

        for (int i = 1;i <=5;i++){
            System.out.println("主執行緒main==》" + i);
        }
    }
@Test
    public void RunnableTest2(){
        //匿名內部類寫法2
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1;i <=5;i++){
                    System.out.println("MyRunnable==》" + i);
                }
            }
        }).start();
 
        for (int i = 1;i <=5;i++){
            System.out.println("主執行緒main==》" + i);
        }
    }
@Test
    public void RunnableTest2(){
        //Lambda 表示式的寫法
        new Thread(()->{
            for (int i = 1;i <=5;i++){
                System.out.println("子執行緒==》" + i);
            }
        }).start();

        for (int i = 1;i <=5;i++){
            System.out.println("主執行緒main==》" + i);
        }
    }

3、實現Callable

​ 前面幾種方法建立的執行緒均沒有返回值,假如執行緒執行完畢後需要返回,可以用這種方式。

  • 定義一個實現Callable介面的類,指定返回資料的型別
  • 重寫call方法,指定返回型別
  • 建立一個Callable物件,注意該物件不是還不是任務物件
  • Callable型別的物件封裝成FutureTask型別的物件
  • 把任務物件交給Thread物件
  • 獲取執行緒執行完畢後返回的結果

/**
 * 1、定義一個實現Callable介面的類,指定返回資料的型別
 */
public class MyCallable implements Callable<String> {
    int n;
    public MyCallable(int n){
        this.n = n;
    }
   // 2、重寫call方法,指定返回型別
    @Override
    public String call() throws Exception {
        int sum = 0;
        for(int i = 1; i < this.n; i++){
            sum += i;
        }
        return "1 - " + n + "的和為:" + sum;
    }
}

    @Test
    public void CallableTest() throws ExecutionException, InterruptedException {
        // 3、建立一個Callable物件,注意該物件不是還不是任務物件
        Callable<String> callable= new MyCallable(10);
        // 4、把Callable型別的物件封裝成FutureTask型別的物件
        // futureTask才是執行緒任務物件
        FutureTask<String> futureTask  = new FutureTask<>(callable);
        // 5、把任務物件交給Thread物件
        new Thread(futureTask).start();
        // 6、獲取執行緒執行完畢後返回的結果
        // 注意:如果上面執行緒還沒執行完畢就執行到該條語句,則主執行緒將會進入阻塞狀態,
        // 等待該子執行緒執行完畢才開始執行
        String s1 = futureTask.get();
        System.out.println(s1);
    }
1 - 10的和為:45

程序已結束,退出程式碼0
    
優點:執行緒任務類只是實現介面,可以繼續繼承類和實現介面,可延伸性強,
    可以線上程執行完畢就去獲取執行緒執行的結果。
缺點:程式碼複雜一點。

二、Thread常用方法

Thread提供的常用方法 說明
public void run() 執行緒的任務方法
public void start() 啟動執行緒
public String getName() 獲取當前執行緒的名稱,執行緒名稱預設是Thread-索引
public void setName(String name) 為執行緒設定名稱
public static Thread currentThread() 獲取當前執行的執行緒物件
public static void sleep(long time) 讓當前執行的執行緒休眠多少毫秒後再執行
public final void join() 讓呼叫當前這個方法的執行緒先執行玩
Thread提供的常見構造器 說明
public Thread(String name) 可以為當前執行緒指定名稱
public Thread(Runnable target) 封裝Runnable物件為執行緒物件
public Thread(Runnable target, String name) 封裝Runnable物件為執行緒物件,並指定執行緒名稱

三、執行緒安全

什麼是執行緒安全問題?

答:多執行緒同時操作同一臨界資源,導致結果存在的不準確性。

例題:當兩個人同時對同一賬號取錢,其其結果導致第一個人把前全部取走,二第二個人還可以繼續取錢。

/**
 * 共用資源:賬戶餘額
 */
public class Accoud {
    private double accound;

    public Accoud(double accound) {
        this.accound = accound;
    }
    //取錢操作
    public void drowMoney(double i) {
        // 獲取當前取錢人的名字
        String name = Thread.currentThread().getName();
        if(accound < i){
            System.out.println(name + "來取錢,餘額不足");
        }
        else{
            System.out.println(name + "取出額度為:" + i);
            accound -= i;
            System.out.println(name + "取錢後剩餘餘額為:" + accound);
        }
    }
}
public class ThreadTest2 extends Thread{
    Accoud accoud;
    public ThreadTest2(Accoud accoud,String name) {
        super(name);
        this.accoud = accoud;
    }

    @Override
    public void run() {
        //取錢
        accoud.drowMoney(1000);
    }
}

public class Main {
    public static void main(String[] args) {
        //建立臨界資源
        Accoud accoud = new Accoud(1000);
        //建立兩個執行緒,同時存取accoud資源
        new ThreadTest2(accoud,"小紅").start();
        new ThreadTest2(accoud,"小藍").start();
    }
}
小紅取出額度為:1000.0
小藍取出額度為:1000.0
小紅取錢後剩餘餘額為:0.0
小藍取錢後剩餘餘額為:-1000.0

程序已結束,退出程式碼0

四、執行緒同步

什麼是執行緒同步

答:執行緒同步是指一個或多個執行緒必須等待某個或多個執行緒執行完某些操作後才能繼續往下執行,常用於多執行緒同時操作同一臨界資源問題,但某執行緒在操作某一臨界資源時,其他執行緒必須等待。

實現執行緒同步有三種方法:同步程式碼塊、同步方法、lock鎖

1、同步程式碼塊

作用:把存取共用資源的核心程式碼給上鎖,但此執行緒執行完後會自動解鎖,以此保證執行緒安全。

synchronized(同步鎖){
		存取共用資源的核心程式碼
}

對之前取錢操作加同步鎖

public void drowMoney(double i) {
        // 獲取當前取錢人的名字
        String name = Thread.currentThread().getName();
    
        synchronized ("這是同步鎖") {
            if(accound < i){
                System.out.println(name + "來取錢,餘額不足");
            }
            else{
                System.out.println(name + "取出額度為:" + i);
                accound -= i;
                System.out.println(name + "取錢後剩餘餘額為:" + accound);
            }
        }
    }
小紅取出額度為:1000.0
小紅取錢後剩餘餘額為:0.0
小藍來取錢,餘額不足

注意:這個鎖其實是一個物件,這個物件被參照的時候會被標記,解鎖後才會消除標記

所以就有一個問題,如果有小紅、小藍、小黑、小白,四個執行緒,而業務只需要小紅小藍同步,小黑小白同步,如果鎖物件是唯一的,則所有的執行緒都用同一把鎖,則紅藍黑白都會被同步,明明兩組不相干的操作卻要一起同步等待,就會影響效率,所以要想把兩組同步分開,就要加不同的鎖,用 this

public class Main {
    public static void main(String[] args) {
        Accoud accoud1 = new Accoud(1000);
        new ThreadTest2(accoud1,"小紅").start();
        new ThreadTest2(accoud1,"小藍").start();

        Accoud accoud2 = new Accoud(1000);
        new ThreadTest2(accoud2,"小黑").start();
        new ThreadTest2(accoud2,"小白").start();
    }
}
public void drowMoney(double i) {
        // 獲取當前取錢人的名字
        String name = Thread.currentThread().getName(
    	//如果小紅小藍執行緒呼叫這個方法,則這裡的this是指accoud1物件
        //如果小黑小白執行緒呼叫這個方法,則這裡的this是指accoud2物件    
        synchronized (this) {
            if(accound < i){
                System.out.println(name + "來取錢,餘額不足");
            }
            else{
                System.out.println(name + "取出額度為:" + i);
                accound -= i;
                System.out.println(name + "取錢後剩餘餘額為:" + accound);
            }
        }
    }
小白取出額度為:1000.0
小藍取出額度為:1000.0
小白取錢後剩餘餘額為:0.0
小藍取錢後剩餘餘額為:0.0
小黑來取錢,餘額不足
小紅來取錢,餘額不足

用this可以識別呼叫不同物件的執行緒,但如果要在靜態方法裡上鎖,因為靜態方法是直接用類名呼叫的,不需要建立物件,這時可以用這個類的唯一標識當鎖,而這個類的唯一標識是位元組碼(類名.class)

public  static void drowMoney(double i) {
        // 獲取當前取錢人的名字
        String name = Thread.currentThread().getName();
    	//靜態方法用類的位元組碼標識
        synchronized (Accoud.class) {
            if(accound < i){
                System.out.println(name + "來取錢,餘額不足");
            }
            else{
                System.out.println(name + "取出額度為:" + i);
                accound -= i;
                System.out.println(name + "取錢後剩餘餘額為:" + accound);
            }
        }
    }

鎖物件的使用規範

  • 建議使用共用資源作為鎖物件,對於實體方法建議使用this作為鎖物件。
  • 對於靜態方法建議使用位元組碼(類名.class)物件作為鎖物件。

2、同步方法

作用:把存取共用資源的核心方法給上鎖,以此保證執行緒安全。

修飾符 synchronized  返回值型別 方法名(形參列表){
	操作共用資源的程式碼
}

同步方法底層原理

  • 同步方法其實底層也是隱式鎖物件的,只是鎖的範圍是整個方法程式碼。
  • 如果方法是實體方法:同步方法預設用this作為的鎖物件。
  • 如果方法是靜態方法:同步方法預設用類名.class作為鎖物件。
public synchronized void drowMoney(double i) {
        // 獲取當前取錢人的名字
        String name = Thread.currentThread().getName();
        if(accound < i){
            System.out.println(name + "來取錢,餘額不足");
        }
        else{
             System.out.println(name + "取出額度為:" + i);
             accound -= i;
             System.out.println(name + "取錢後剩餘餘額為:" + accound);
        }
    }

3、Lock鎖

  • Lock鎖是JDK5開始提供的一個新的鎖定操作,通過它可以建立出鎖物件進行加鎖和解鎖,更靈活、更方便。

  • Lock是介面,不能直接範例化,可以採用它的實現類ReentrantLock來構建Lock鎖物件。

  • Lock常用方法

    變數型別 方法名稱 說明
    void lock() 獲得鎖。
    void lockInterruptibly () 除非當前執行緒是interrupted,否則獲取鎖定。
    Condition newCondition() 返回一個新Condition繫結到該範例Lock範例。
    boolean tryLock () 只有在呼叫時他是空閒的才能獲取鎖。
    boolean tryLock (long time,TimeUnit unit) 如果鎖在給定的等待時間內是空閒的並且當前執行緒不是interrupted,則獲取鎖。
    void unlock () 釋放鎖定。

五、執行緒池

什麼是執行緒池
答:執行緒池就是一個可以複用執行緒的技術。
不使用執行緒池的問題
使用者每發起一個請求,後臺就需要建立一個新執行緒來處理,下次新任務來了肯定又要建立新執行緒處理的,而建立新執行緒的開銷是很大的,並且請求過多時,肯定會產生大量的執行緒出來,這樣會嚴重影響系統的效能。

誰代表執行緒池

答:JDK5.0起提供了代表執行緒池的介面:ExecutorService。

如何得到執行緒池物件

  • 方式一:使用ExecutorService的實現類ThreadPoolExecutor自建立一個執行緒池物件,
  • 方式二:使用Executors(執行緒池的工具類)呼叫方法返回不同特點的執行緒池物件。

1、ThreadPoolExecutor構造器

public ThreadPoolExecutor(int corePoolsize, //指定執行緒池的核心執行緒的數量
                          int maximumPoolsize,//指定執行緒池的最大執行緒數量
                          long keepAliveTime,//指定臨時執行緒的存活時間。
                          TimeUnit unit, //指定臨時執行緒存活的時間單位(秒、分、時、天)
                          BlockingQueue<Runnable>workQueue,//指定執行緒池的任務佇列
                          ThreadFactory threadFactory,//指定執行緒池的執行緒工廠
                          RejectedExecutionHandler handler)//指定執行緒池的任務拒絕策略
    													   //(執行緒都在忙,任務佇列也滿了的時															//候,新任務來了該怎麼處理

關於核心執行緒池的數量指定多少合適?

  • 對於計算機密集型的任務(使用CPU頻繁),主機邏輯處理器核數 + 1
  • 對於IO密集型的任務,主機邏輯處理器核數 * 2
public class Main {
    public static void main(String[] args) {
        //1、通過TreadPoolExecutor建立一個執行緒池物件,
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
                Executors.defaultThreadFactory(),//使用預設的執行緒池建立方法
                new ThreadPoolExecutor.AbortPolicy());
    }
}

執行緒池的注意事項

1、臨時執行緒什麼時候建立?
答:新任務提交時發現核心執行緒都在忙,任務佇列也滿了,並且還可以建立臨時執行緒,此時才會建立臨時執行緒。
2、什麼時候會開始拒絕新任務
答:核心執行緒和臨時執行緒都在忙,任務佇列也滿了,新的任務過來的時候才會開始拒絕任務。

2、ExecutorService的常用方法

方法名稱 說明
void execute(Runnable comand) 執行 Runnable 任務
Future submit(Callable task) 執行 Callable 任務,返回未來任務物件,用於獲取執行緒返回的結果
void shutdown() 等全部任務執行完畢後,再關閉執行緒池
List shutdownNow 立即關閉執行緒池,停止正在執行的任務,並返回佇列中未執行任務
public class MyRunable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "正在工作");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3、執行緒池處理 Runnable任務

public class Main {
    public static void main(String[] args) {
        //1、通過TreadPoolExecutor建立一個執行緒池物件,
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
                Executors.defaultThreadFactory(),//使用預設的執行緒池建立方法
                new ThreadPoolExecutor.AbortPolicy());
        Runnable target = new MyRunable();
        // 執行緒池最開始初始化時是沒有執行緒的
        // 當某執行緒任務結束後執行緒池不會自動銷燬執行緒
        // 當執行緒池接受到新任務後如果沒有空閒執行緒會自動建立新執行緒處理這個任務
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
    }
}
pool-1-thread-3正在工作
pool-1-thread-2正在工作
pool-1-thread-1正在工作
pool-1-thread-3正在工作
pool-1-thread-2正在工作

//執行緒一旦建立執行緒池就不會自動關閉,所以整個程式會一直處於執行狀態不會停

新增關閉執行緒的方法

public class Main {
    public static void main(String[] args) {
        //1、通過TreadPoolExecutor建立一個執行緒池物件,
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
                Executors.defaultThreadFactory(),//使用預設的執行緒池建立方法
                new ThreadPoolExecutor.AbortPolicy());
        Runnable target = new MyRunable();
        // 執行緒池最開始初始化時是沒有執行緒的
        // 當某執行緒任務結束後執行緒池不會自動銷燬執行緒
        // 當執行緒池接受到新任務後如果沒有空閒執行緒會自動建立新執行緒處理這個任務
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);

        pool.shutdown();//等全部任務執行完畢後,再關閉執行緒池
        // pool.shutdownNow();//立即關閉執行緒池,停止正在執行的任務,並返回佇列中未執行任務
    }
}
pool-1-thread-2正在工作
pool-1-thread-1正在工作
pool-1-thread-3正在工作
pool-1-thread-2正在工作
pool-1-thread-1正在工作

程序已結束,退出程式碼0

臨時執行緒的建立時機

  • 當任務佇列未滿時不建立臨時執行緒
public class Main {
    public static void main(String[] args) {
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
                Executors.defaultThreadFactory(),//使用預設的執行緒池建立方法
                new ThreadPoolExecutor.AbortPolicy());
        Runnable target = new MyRunable();
        //模擬臨時執行緒建立時機
        //3個核心執行緒在忙
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);

        // 4個任務佇列滿了
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);

        //如果還有任務請求,則開始建立臨時執行緒
        // pool.execute(target);
        

        pool.shutdown();//等全部任務執行完畢後,再關閉執行緒池
        // pool.shutdownNow();//立即關閉執行緒池,停止正在執行的任務,並返回佇列中未執行任務
    }
}
pool-1-thread-3正在工作
pool-1-thread-1正在工作
pool-1-thread-2正在工作
pool-1-thread-1正在工作
pool-1-thread-2正在工作
pool-1-thread-3正在工作
pool-1-thread-1正在工作

程序已結束,退出程式碼0
  • 當核心執行緒在忙且任務佇列已滿時建立建立臨時執行緒
package org.example;

import org.example.ExecutorService.MyRunable;

import java.util.concurrent.*;

public class Main {
    public static void main(String[] args) {
        //1、通過TreadPoolExecutor建立一個執行緒池物件,
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
                Executors.defaultThreadFactory(),//使用預設的執行緒池建立方法
                new ThreadPoolExecutor.AbortPolicy());
        Runnable target = new MyRunable();
        //模擬臨時執行緒建立時機
        //3個核心執行緒在忙
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);

        // 4個任務佇列滿了
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);

        //如果還有任務請求,則開始建立臨時執行緒
        pool.execute(target);
        pool.execute(target);
        



        pool.shutdown();//等全部任務執行完畢後,再關閉執行緒池
        // pool.shutdownNow();//立即關閉執行緒池,停止正在執行的任務,並返回佇列中未執行任務
    }
}
pool-1-thread-1正在工作
pool-1-thread-2正在工作
pool-1-thread-3正在工作
pool-1-thread-4正在工作//建立了4、5的臨時執行緒
pool-1-thread-5正在工作
pool-1-thread-1正在工作
pool-1-thread-3正在工作
pool-1-thread-4正在工作
pool-1-thread-2正在工作

程序已結束,退出程式碼0

新任務拒絕策略

  • 當核心執行緒和臨時執行緒在滿,且任務列表滿時,再有任務請求則呼叫拒絕策略
  • 常用策略
策略 詳解
ThreadPoolExecutor.AbortPolicy 丟棄任務並丟擲RejectedExecutionException.異常。是預設的策略
ThreadPoolExecutor.DiscardPolicy: 丟棄任務,但是不丟擲異常這是不推薦的做法
ThreadPoolExecutor.DiscardoldestPolicy 拋棄佇列中等待最久的任務然後把當前任務加入佇列中
ThreadPoolExecutor.CallerRunsPolicy 由主執行緒負責呼叫任務的ru0方法從而繞過執行緒池直接執行
public class Main {
    public static void main(String[] args) {
        //1、通過TreadPoolExecutor建立一個執行緒池物件,
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
                Executors.defaultThreadFactory(),//使用預設的執行緒池建立方法
                new ThreadPoolExecutor.CallerRunsPolicy());//拒絕策略
        Runnable target = new MyRunable();
        //模擬臨時執行緒建立時機
        //3個核心執行緒在忙
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);

        // 4個任務佇列滿了
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);
        pool.execute(target);

        //如果還有任務請求,則開始建立臨時執行緒
        pool.execute(target);
        pool.execute(target);

        //還有任務請求會自動呼叫指定的拒絕策略
        pool.execute(target);
        
        pool.shutdown();//等全部任務執行完畢後,再關閉執行緒池
        // pool.shutdownNow();//立即關閉執行緒池,停止正在執行的任務,並返回佇列中未執行任務
    }
}
//呼叫CallerRunsPolicy拒絕策略
pool-1-thread-2正在工作
main正在工作
pool-1-thread-1正在工作
pool-1-thread-5正在工作
pool-1-thread-3正在工作
pool-1-thread-4正在工作
pool-1-thread-1正在工作
pool-1-thread-2正在工作
pool-1-thread-4正在工作
pool-1-thread-3正在工作

程序已結束,退出程式碼0

4、執行緒池處理 Callable 任務

public class MyCallable implements Callable<String> {
    int n;
    public MyCallable(int n){
        this.n = n;
    }

   // 2、重寫call方法,指定返回型別
    @Override
    public String call() throws Exception {
        int sum = 0;
        for(int i = 1; i <= this.n; i++){
            sum += i;
        }
        return Thread.currentThread().getName() + "求出》1-" + n + "的和為:" + sum;
    }
}

public class Main {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = new ThreadPoolExecutor(3, 5, 8,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(4),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy());
        //使用執行緒處理Callable任務
        Future<String> f1 = pool.submit(new MyCallable(100));
        Future<String> f2 = pool.submit(new MyCallable(200));
        Future<String> f3 = pool.submit(new MyCallable(300));
        Future<String> f4 = pool.submit(new MyCallable(400));
        System.out.println(f1.get());
        System.out.println(f2.get());
        System.out.println(f3.get());
        System.out.println(f4.get());

        pool.shutdown();//等全部任務執行完畢後,再關閉執行緒池
    }
}
pool-1-thread-1求出》1-100的和為:5050
pool-1-thread-2求出》1-200的和為:20100
pool-1-thread-3求出》1-300的和為:45150
pool-1-thread-2求出》1-400的和為:80200

程序已結束,退出程式碼0

5、Executors 工具類實現執行緒池

  • Executors 工具底層都是通過執行緒池的實現類ThreadPoolExecutor建立的執行緒池物件。
  • 不建議使用這個Executors工具類實現執行緒池,因為在大型並行系統環境中使用Executors如果不注意可能會出現系統風險。用原始的建立方法可以讓開發者更好的理解執行緒池執行流程,也可以控制執行緒池一些資料來規避風險。
  • 常用方法
方法名稱 說明
public static ExecutorService newFixedThreadPool(int nThreads) 建立固定執行緒數量的執行緒池,如果某個執行緒因為執行異常而結束,那麼執行緒池會補充一個新執行緒替代它。
public static ExecutorService newsingleThreadExecutor() 建立只有一個執行緒的執行緒池物件,如果該執行緒出現異常而結束,那麼執行緒池會補充一個新執行緒。
public static ExecutorService newCachedThreadPool() 執行緒數量隨著任務增加而增加,如果執行緒任務執行完畢且空閒了6s則會被回收掉。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolsize) 建立一個執行緒池,可以實現在給定的延遲後執行任務,或者定期執行任務。

六、執行緒的生命週期

Java總共定義了6種狀態。

6種狀態都定義在Thread類的內部列舉類中

public class Thread{
    ...
	public enum State{
		NEW,	//新建狀態
		RUNNABLE, //執行狀態
		BLOCKED, //阻塞狀態
		WAITING, //計時等待狀態
		TIMED WAITING, //無限等待狀態
		TERMINATED; //終止狀態
	}
}
執行緒狀態 說明
NEW(新建) 執行緒剛被建立,但是並未啟動。
Runnable(可執行) 執行緒已經呼叫了start(),等待CPU排程。
B1 ocked(鎖阻塞) 執行緒在執行的時候未競爭到鎖物件,則該執行緒進入Blocked狀態。
Waiting(無限等待) 一個執行緒進入Waiting狀態,另一個執行緒呼叫notify或者notifyAll方法才能夠喚醒
Timed Waiting(計時等待) 同waiting狀態,有幾個方法(sleep,wait)有超時引數,呼叫他們將進入Timed Waiting狀態。
Teminated(被終止) 因為ru方法正常退出而死亡,或者因為沒有捕獲的異常終止了run方法而死亡。

注意得到鎖的執行緒呼叫 sleep() 進入等待狀態,期間不會釋放鎖,而 wait() 會釋放鎖。