天天看點

JUC|線程的四種建立方式詳解1、繼承Thread類建立線程類2、實作Runnable接口建立線程類3、通過Callable和Future建立線程4、使用線程池建立(重要)每日小結

大家好,我是程式猿小馬,滬漂一族!

寫文章就是對于平時的總結以及大家共同學習進步,早日碼出各自的夢想😊

JUC|線程的四種建立方式詳解1、繼承Thread類建立線程類2、實作Runnable接口建立線程類3、通過Callable和Future建立線程4、使用線程池建立(重要)每日小結

坐标:

郊野公園

文章目錄

  • 1、繼承Thread類建立線程類
  • 2、實作Runnable接口建立線程類
  • 3、通過Callable和Future建立線程
  • 4、使用線程池建立(重要)
  • 每日小結

1、繼承Thread類建立線程類

  • 定義Thread類的子類,并重寫該類的run方法,該run方法的方法體就代表了線程要完成的任務。是以把run()方法稱為執行體。
  • 建立Thread子類的執行個體,即建立了線程對象。
  • 調用線程對象的start()方法來啟動該線程。

示例代碼:

public class Thread_1 {
    public static void main(String[] args) {

        Thread.currentThread().setName("主線程");
        System.out.println(Thread.currentThread().getName()+":"+"輸出的結果");
        //建立一個新線程
        ThreadDemo1 thread1 = new ThreadDemo1();
        //為線程設定名稱
        thread1.setName("線程一(繼承Thread類)");
        //開啟線程
        thread1.start();


    }
}
class ThreadDemo1 extends Thread{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":"+"輸出的結果");
    }

}
           

傳回結果:

主線程:輸出的結果
線程一(繼承Thread類):輸出的結果
           

2、實作Runnable接口建立線程類

  • 定義runnable接口的實作類,并重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執行體。
  • 建立 Runnable實作類的執行個體,并依此執行個體作為Thread的target來建立Thread對象,該Thread對象才是真正的線程對象。
  • 調用線程對象的start()方法來啟動該線程。

示例代碼:

public class Thread_2 {
    public static void main(String[] args) {
        Thread.currentThread().setName("主線程");
        System.out.println(Thread.currentThread().getName()+":"+"輸出的結果");
        //建立一個新線程
        Thread thread2 = new Thread(new ThreadDemo2());
        //為線程設定名稱
        thread2.setName("線程二(實作Runnable接口)");
        //開啟線程
        thread2.start();
    }
}


class ThreadDemo2 implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":"+"輸出的結果");
    }

}

           

傳回結果:

主線程:輸出的結果
線程二(實作Runnable接口):輸出的結果
           

3、通過Callable和Future建立線程

  • 建立Callable接口的實作類,并實作call()方法,該call()方法将作為線程執行體,并且有傳回值。
  • 建立Callable實作類的執行個體,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方法的傳回值。
  • 使用FutureTask對象作為Thread對象的target建立并啟動新線程。
  • 調用FutureTask對象的get()方法來獲得子線程執行結束後的傳回值。

示例代碼:

public class Thread_3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Thread.currentThread().setName("主線程");
        System.out.println(Thread.currentThread().getName()+":"+"輸出的結果");
        //建立FutureTask的對象
        FutureTask<String> task = new FutureTask<String>(new ThreadDemo3());
        //建立Thread類的對象
        Thread thread3 = new Thread(task);
        thread3.setName("線程三(實作Callable接口和Future)");
        //開啟線程
        thread3.start();
        //擷取call()方法的傳回值,即線程運作結束後的傳回值
        String result = task.get();
        System.out.println(result);

    }
}

class ThreadDemo3 implements Callable<String> {

    @Override
    public String call() throws Exception {
        System.out.println(Thread.currentThread().getName()+":"+"輸出的結果");
        return Thread.currentThread().getName()+":"+"傳回的結果";
    }
}
           

傳回結果:

主線程:輸出的結果
線程三(實作Callable接口和Future):輸出的結果
線程三(實作Callable接口和Future):傳回的結果
           

4、使用線程池建立(重要)

為什麼不用預設建立的線程池?

線程池建立的方法有:固定數的,單一的,可變的,那麼在實際開發中,應該使用哪個?

我們一個都不用,在生産環境中是使用自己自定義的

為什麼不用Executors中JDK提供的?

根據阿裡巴巴手冊:并發控制這章

  • 線程資源必須通過線程池提供,不允許在應用中自行顯式建立線程
    • 使用線程池的好處是減少在建立和銷毀線程上所消耗的時間以及系統資源的開銷,解決資源不足的問題,如果不使用線程池,有可能造成系統建立大量同類線程而導緻消耗完記憶體或者“過度切換”的問題
  • 線程池不允許使用Executors去建立,而是通過ThreadToolExecutors的方式,這樣的處理方式讓寫的同學更加明确線程池的運作規則,規避資源耗盡的風險
    • Executors傳回的線程池對象弊端如下:
      • FixedThreadPool和SingleThreadPool:

        運作的請求隊列長度為:Integer.MAX_VALUE,可能會堆積大量的請求,進而導緻OOM

      • CacheThreadPool和ScheduledThreadPool

        運作的請求隊列長度為:Integer.MAX_VALUE,可能會堆積大量的請求,進而導緻OOM

是以下面讓我們手寫一個線程池

示例代碼:

public class Thread_4 {
    public static void main(String[] args) {

        // 手寫線程池
        final Integer corePoolSize = 2;
        final Integer maximumPoolSize = 5;
        final Long keepAliveTime = 1L;

        // 自定義線程池,隻改變了LinkBlockingQueue的隊列大小
        ExecutorService executorService = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy());

        // 模拟10個使用者來辦理業務,每個使用者就是一個來自外部請求線程
        try {

            // 循環十次,模拟業務辦理,讓5個線程處理這10個請求
            for (int i = 0; i < 10; i++) {
                final int tempInt = i;
                executorService.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t 給使用者:" + tempInt + " 辦理業務");
                    try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}// 這一樣可以看出不用的線程處理,不然可能都是第一個線程處理了
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown();
        }
    }
}
           

傳回結果:

pool-1-thread-1	 給使用者:0 辦理業務
pool-1-thread-2	 給使用者:1 辦理業務
pool-1-thread-3	 給使用者:5 辦理業務
pool-1-thread-4	 給使用者:6 辦理業務
pool-1-thread-5	 給使用者:7 辦理業務
pool-1-thread-1	 給使用者:4 辦理業務
pool-1-thread-2	 給使用者:8 辦理業務
pool-1-thread-5	 給使用者:9 辦理業務
           

每日小結

  • 其實大家對線程池的建立有個了解,一般面試的時候會問一下,但是第四種方式後面小馬會單獨詳細的做一期關于它的,日常開發中第四種應該是我們常用的方式,其他三種了解即可,面試可能會問。
以上就是小馬今天要發的内容,歡迎大家互相學習,共同進步,也可以在評論區互動哦!👇