天天看點

Java中有哪幾種方式來建立線程來執行任務

作者:架構小哥

前言

在java中一個線程是使用Thread的執行個體來表示,所有線程對象都必須是Thread的子類執行個體或是Thread的執行個體,它位于java.lang包中,是Java中非常常用也是非常重要的一個基礎類。Thread類實作了Runnable接口,它有不少非常重要的方法和屬性:

Java中有哪幾種方式來建立線程來執行任務
Java中有哪幾種方式來建立線程來執行任務

那麼建立Java的線程一般有哪幾種方式呢?

一、繼承Thread類建立線程類

1. 定義Thread類的子類ThreadDemo,并重寫該Thread類的run()方法,在run()方法中執行該線程的主要邏輯。

2. 在主線程上建立Thread子類的執行個體,即建立了一個線程對象。

3. 調用線程對象的start()方法來啟動該線程。

// 繼承Thread類,建立一個新的線程類
public class ThreadDemo extends Thread{
    // 重寫了Thread類的run()方法,将需要并發執行的使用者業務代碼編寫在繼承的run()方法中
    @Overrite
    public void run(){
        System.out.println(getName()+" 這裡執行線程的業務邏輯");
    }
}           
public class AppDemo {
    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        // 啟動線程
        threadDemo.start();
        System.out.println("線程名:"+Thread.currentThread().getName()+" 運作結束");
    }
}           

執行結果:

線程名:main 運作結束

Thread-0 這裡執行線程的業務邏輯

二、實作Runnable接口,建立線程目标類

有了Thread類了,為什麼還需要多一個Runnable的接口方式來建立一個線程呢?那是因為Java繼承的一個特征,extend隻能一個父類,當一個子類已經繼承了一個父類時,此時你希望這個類成為一個線程類,是不行的,但它能實作Runnable接口。

1.定義Runnable接口的實作類,并實作run()方法,在run()方法中執行該線程的主要邏輯。

⒉.建立Runnable實作類的執行個體,并将其作為Thread的target來建立Thread對象,Thread對象為線程對象。

3.調用線程對象的start()方法來啟動該線程。

public class RunnableThreadDemo implements Runnable {

    // 重寫了Runnable類的run()方法,将需要并發執行的使用者業務代碼編寫在繼承的run()方法中
    @0verrite
    public void run( ){
        System.out.println(Thread.currentThread().getName()+" 這裡執行線程的業務邏輯" )
    }
}           
public class AppDemo {
    public static void main(String[] args) {
        RunnableThreadDemo threadDemo = new RunnableThreadDemo();
        // 啟動線程
        threadDemo.start();
        System.out.println("線程名:"+Thread.currentThread().getName()+" 運作結束");
    }
}           

執行結果:

線程名:main 運作結束

Thread-0 這裡執行線程的業務邏輯

三、使用匿名内部類,實作建立Thread子類

跟方式一類似,隻是不需要建立一個單獨的類檔案來繼承Thread,而是使用了匿名類來完成,代碼如下:

public class AppDemo {
    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                System.out.println(getName()+" 這裡執行線程的業務邏輯");
            }
        };
        thread.start();
        System.out.println("線程名:"+Thread.currentThread().getName()+" 運作結束");
    }
}           

執行結果:

線程名:main 運作結束

Thread-0 這裡執行線程的業務邏輯

四、使用匿名内部類,實作Runnable接口

跟方式二類似,差別在于使用匿名類來完成,代碼如下:

public class AppDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" 這裡執行線程的業務邏輯" );
            }
        });
        thread.start();
        System.out.println("線程名:"+Thread.currentThread().getName()+" 運作結束");
    }
}           

執行結果:

Thread-0 這裡執行線程的業務邏輯

線程名:main 運作結束

什麼?這跟方式二執行的結果不一樣?這裡是沒問題的,這裡是兩個線程在跑着,哪個線程先跑是跟cpu的排程有關,跟代碼的先後無關。這點請緊記。

五、使用lambda表達式來完成

使用lambda表達式使得代碼更簡潔,實作其實跟方式三一樣,隻是使用lambda來代替匿名類。

public class AppDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            System.out.println(Thread.currentThread().getName()+" 這裡執行線程的業務邏輯");
        });
        thread.start();
        System.out.println("線程名:"+Thread.currentThread().getName()+" 運作結束");
    }
}           

執行結果:

線程名:main 運作結束

Thread-0 這裡執行線程的業務邏輯

六、實作Callable接口

繼承Thread類或者實作Runnable接口這兩種方式來建立線程類有一個缺陷,就是不能擷取異步執行的結果,即在主線程啟動子線程後,能run起來就分道揚镳了,但在很多場景上需要擷取到異步執行的結果,這個時候就需要實作Callable接口了。這是在1.5之後提供的一種建立多線程的方法,通過Callable接口和FutureTask類互相作用來完成線程執行以及在主線程中擷取執行的結果。

Callable接口位于java.util.concurrent包中:

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}           

建立步驟如下:

  1. 首先定義一個 Callable 的實作類,并實作call方法。
  2. call方法是帶傳回值的。然後通過FutureTask的構造方法,把這個Callable 實作類傳進去。
  3. 把 FutureTask作為 Thread 類的 target ,建立Thread線程對象。
  4. 通過FutureTask 的get方法擷取線程的執行結果。
public class CallableTaskDemo implements Callable {
    // 編寫好異步執行的具體邏輯,可以有傳回值。
    // (Runnable接口中的run()方法是沒有傳回值得,Callable接口的call()方法有傳回值)
    @Override
    public Long call() throws Exception {
        Long startTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+" 線程開始運作");
        Thread.sleep(1000);

        for(int i=0;i<100000000;i++){
            int j = i*10000;
        }

        Long endTime = System.currentTimeMillis();
        Long used = endTime-startTime;
        System.out.println(Thread.currentThread().getName()+" 線程結束運作");
        return used;
    }
}           
public class AppDemo {
    public static void main(String[] args) throws InterruptedException {
        CallableTaskDemo callableTaskDemo = new CallableTaskDemo();
        FutureTask<Long> futureTask = new FutureTask<Long>(callableTaskDemo);
        Thread thread = new Thread(futureTask,"callableTaskThread");
        thread.start();

        Thread.sleep(500);

        System.out.println("main線程執行一會");
        for(int i=0;i<100000000/2;i++){
            int j = i*10000;
        }
        // 擷取并發任務的執行結果
        try {
            System.out.println(thread.getName()+" 線程占用時間:"+futureTask.get());
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}           

執行結果:

callableTaskThread 線程開始運作

main線程執行一會

callableTaskThread 線程結束運作

callableTaskThread 線程占用時間:1008

七、使用自帶線程池

使用線程池的一個很重要的原因是線程的複用性。我們知道線程資源在伺服器上來看,是一種非常昂貴的資源,如果使用不當,伺服器的負載是非常高,并且可能會出現超負荷的情況,往往會導緻伺服器當機等問題,是以線程的合理配置設定就變得是一個非常有意思的話題。線程池往往就能很好地解決這個問題,設定一個線程池,往線程池中注入固定的線程數,超過的線程的任務數,要不等待,要不抛棄。大大地複用了線程,減少線程頻繁建立與銷毀,提高了伺服器的可用率。

Java線上程池上也有多種類型的支援,在不同的場景下可選擇不同類型的線程池。

  • FixThreadPool(int n);--固定大小的線程池

    使用于負載比較重的伺服器,滿足資源管理需求而對資源有強制限制的需求

  • SingleThreadPoolExecutor --單線程池

    需要保證順序執行各個任務的場景,任務按先進先出的形式來執行

  • CashedThreadPool() -- 緩存線程池

    适用于送出短期的異步小程式以及負載較輕的伺服器。當送出任務速度高于線程池中任務處理速度時,緩存線程池會不斷地建立線程。調用execute 将重用以前構造的線程(如果線程可用)。如果現有線程沒有可用的,則建立一個新線程并添加到池中。終止并從緩存中移除那些已有 60 秒鐘未被使用的線程。

  • ScheduledExecutor --定時及周期性的任務執行的線程池

    适用于延遲或排程類的工作,多數情況可替代Timer類

一般的使用方式如下:

  1. 首先,定義一個 Runnable 的實作類,重寫run方法
  2. 然後建立一個擁有固定線程數的線程池
  3. 最後通過ExecutorService對象的execute 方法傳入線程對象
public class RunnableTaskDemo implements Runnable {
    @Override
    public void run() {
        for(int i=0;i<3;i++){
            System.out.println(Thread.currentThread().getName()+" 輪次:"+i);

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }           
public class CallableTaskDemo implements Callable {
    @Override
    public Long call() throws Exception {
        Long startTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+" 線程開始運作");
        for(int i=0;i<3;i++){
            System.out.println(Thread.currentThread().getName()+" 輪次:"+i);
            Thread.sleep(1000);
        }
        Long used = System.currentTimeMillis()-startTime;
        System.out.println(Thread.currentThread().getName()+" 線程結束運作");
        return used;
    }
}           
public class AppDemo {
    private static ExecutorService threadpool = Executors.newFixedThreadPool(3);

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 1、執行Runnbale類型的target目标執行個體,無傳回
        threadpool.execute(new RunnableTaskDemo());

        //2、 執行Runnbale類型的target目标執行個體,無傳回,内部類形式
        threadpool.execute(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<3;i++){
                    System.out.println(Thread.currentThread().getName()+" 輪次:"+i);

                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }       
        });

        // 3、送出Callable執行目标執行個體,有傳回
        Future future = threadpool.submit(new CallableTaskDemo());
        System.out.println("異步執行的結果為:" + future.get());
    }
}           

執行結果:

pool-1-thread-1 輪次:0

pool-1-thread-2 輪次:0

pool-1-thread-3 線程開始運作

pool-1-thread-3 輪次:0

pool-1-thread-2 輪次:1

pool-1-thread-1 輪次:1

pool-1-thread-3 輪次:1

pool-1-thread-2 輪次:2

pool-1-thread-1 輪次:2

pool-1-thread-3 輪次:2

pool-1-thread-3 線程結束運作

異步執行的結果為:3002

繼續閱讀