併發基礎-第01章-實現執行緒的正確方式

niewj發表於2023-01-09

1. 問題的引出:實現執行緒有幾種方式?2種?5種?

正確答案:兩種

  • 實現Runnable介面
  • 繼承Thread類

1.1 Thread類中的run()

Thread類的原始碼:

private Runnable target;
@Override
public void run() {
    if (target != null) {
        target.run();
    }
}
Thread類有個Runnable的target引用,如果構造器傳入了target且不為null,就執行它的run();但前提是它有機會執行--什麼意思呢?

1.2 既實現了Thread又實現了Runnable介面執行誰的run()

且看如下一段程式碼:MyTask類是Runnable介面的實現;MyThread是Thread類的子類;如果初始化MyThread時把MyTask作為Runnable傳入到target,會執行誰的run()?

package com.niewj.basic.createthread;

/**
 * 既實現Runnable又繼承Thread
 *
 * @author niewj
 * @description
 * @copyright © 2022 niewj.com
 * @department 研發
 * @date 2023/1/3 23:14
 */
public class RunnableThread {
    
    public static void main(String[] args) {
        // 1. 實現了Runnable
        MyTask task = new MyTask();
        // 2. 實現了Runnable,也繼承了Thread, thread2輸出什麼?
        Thread thread = new MyThread(task);
        thread.start();
    }


    // MyThread是繼承Thread的類
    static class MyThread extends Thread {
        public MyThread(Runnable runnable) {
            super(runnable);
        }

        @Override
        public void run() {
            System.out.println("==============MyThread.run()====");
        }
    }

    // MyTask是實現Runnable的介面
    static class MyTask implements Runnable {
        @Override
        public void run() {
            System.out.println("==============Runnable.run()====");
        }
    }
}
執行結果如下:可見執行了Thread的run()而不是Runnable的;
這裡比較迷惑的是,不是說target!=null就執行它的run()嗎?關鍵是這裡不是判斷target的問題,而是整個Thread子類的run()方法被子類覆蓋了,沒有機會執行到MyThread父類Thread的run()方法了,這才是關鍵!
==============MyThread.run()====

1.3 簡寫成如下的程式碼再理解一遍:

package com.niewj.basic.createthread;

/**
 * 既實現Runnable又繼承Thread
 *
 * @author niewj
 * @description
 * @copyright © 2022 niewj.com
 * @department 研發
 * @date 2023/1/3 23:14
 */
public class RunnableThread {

    public static void main(String[] args) {
        // 1. 實現了Runnable
        Thread thread = new Thread(() -> System.out.println("==============Runnable.run()====")) {
            @Override
            public void run() {
                System.out.println("==============MyThread.run()====");
            }
        };
        // 2. 實現了Runnable,也繼承了Thread, thread2輸出什麼?
        thread.start();
    }
}

控制檯:

==============MyThread.run()====
是子類run()覆蓋了Thread的run(),所以Thread類的run()中的邏輯都不會執行到。

2. 實現執行緒方式的其他說法

  1. 說法1:Callable介面
  2. 說法2:執行緒池方式
  3. 說法3:定時器類

2.1 Callable介面

Callable介面本身並不會建立執行緒,最終還是借用了Runnable介面來完成執行緒的作用。別忘了Callable介面的用法:

  • Callable介面要寄生在FutureTask裡

    Callable介面要寄生在FutureTask裡才能實現執行緒,最終靠的也是Runnable,因為FutureTask是Runnable介面的子類。所以只能說Callable是借Runnable完成一次復出而已;
還有說Executors裡使用Callable,實際上也是使用ThreadPoolExecutor執行緒池來實現,內部也是FutureTask,本質上也一樣!

2.2 Executors和執行緒池ThreadPoolExecutor

Executors是一個執行緒池的便捷工具類,內部的方法本質上還是透過持了執行緒池ExecutorService的刀來搶的執行緒的劫,而ExecutorService就是執行緒池的抽象介面,它對於執行緒的實現,也是靠執行緒建立工廠,最終在ThreadPoolExecutor裡也是透過Runnable介面和new Thread來完成的。

2.3 定時器類實現執行緒的說法

2.3.1 TimerTask抽象類

TimerTask抽象類本身也是Runnable的實現類,就不用說了,見原始碼:

public abstract class TimerTask implements Runnable {}

2.3.2 Timer類

Timer類中是持有一個TimerThread類,TimerThread類呢是Thread的子類,本質上也是Thread:

private final TimerThread thread = new TimerThread(queue);
class TimerThread extends Thread {}

所以,最終還是隻有兩種方式實現執行緒:實現Runnable介面和整合Thread類。

相關文章