1、實現多線程的方法是1種,2種還是4種?
Oracle官網的文檔是如何寫的?
- 方法一:實現Runnable接口
- 方法二:繼承Thread類
1.1 實現示例
- 實現Runnable接口
/**
* RunnableStyle
*
* @author venlenter
* @Description: 用Runnable方式創建線程
* @since unknown, 2020-03-23
*/
public class RunnableStyle implements Runnable {
@Override
public void run() {
System.out.println("用Runnable方法實現線程");
}
public static void main(String[] args) {
Thread thread = new Thread(new RunnableStyle());
thread.start();
}
}
//輸出結果
用Runnable方法實現線程
- 繼承Thread類
/**
* ThreadStyle
*
* @author venlenter
* @Description: 用Thread方法實現線程
* @since unknown, 2020-03-23
*/
public class ThreadStyle extends Thread {
@Override
public void run() {
System.out.println("用Thread方法實現線程");
}
public static void main(String[] args) {
Thread thread = new ThreadStyle();
thread.start();
}
}
//輸出結果
用Thread方法實現線程
1.2 兩種方法的對比
方法1(實現Runnable接口)更好
兩種方法的本質對比
- (Runnable):最終調用target.run();
實際上執行的是target的run方法
Thread.java
——————————————————————————————————————————
private Runnable target; //target是我們傳入的new RunnableStyle()
@Override
public void run() {
if (target != null) {
target.run();
}
}
——————————————————————————————————————————
- (Thread):run()整個都被重寫
調用的是ThreadStyle override的run方法
2、同時使用兩種方法:正確實現方法的總結
- 實現了Runnable的run方法,但最終被Thread override的run覆蓋,所以只打印了Thread的
/**
* BothRunnableThread
*
* @author venlenter
* @Description: 同時使用Runnable和Thread
* @since unknown, 2020-03-23
*/
public class BothRunnableThread {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我來自Runnable");
}
}) {
@Override
public void run() {
System.out.println("我來自Thread");
}
}.start();
}
}
//輸出結果
我來自Thread
2.1 總結
通常我們可以分爲2類,Oracle官方文檔也是這樣描述
準確的說,創建線程只有1種方式,那就是構造Thread類,而實現線程的執行單元有2種方式
- 方法一:實現Runnable接口,重寫run方法,並把Runnable實例傳給Thread類
- 方法二:重寫Thread的run方法(繼承Thread類)
3、典型錯誤觀點
- 線程池創建線程也算是一種新建線程的方式(本質也是通過Thread的方式)
/**
* ThreadPool5
*
* @author venlenter
* @Description: 線程池創建線程的方法
* @since unknown, 2020-03-24
*/
public class ThreadPool5 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
executorService.submit(new Task(){});
}
}
}
class Task implements Runnable {
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
//輸出結果
pool-1-thread-1
pool-1-thread-2
...
pool-1-thread-999
pool-1-thread-1000
- 通過Callable和FutureTask創建線程,也算是一種新建線程的方式(本質實現了Runnable接口)
- “無返回值”是實現Runnable接口,“有返回值”是實現callable接口,所以callable是新的實現線程的方式(同上,本質都是實現了Runnable)
- 定時器(TimerTask implements Runnable)
/**
* DemoTimmerTask
*
* @author venlenter
* @Description: 定時器創建線程,定時1s打印當前線程名
* @since unknown, 2020-03-29
*/
public class DemoTimmerTask {
public static void main(String[] args) {
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}, 1000, 1000);
}
}
//輸出結果
Timer-0
Timer-0
Timer-0
- 匿名內部類
/**
* AnonymouslnnerClassDemo
*
* @author venlenter
* @Description: 匿名內部類創建線程
* @since unknown, 2020-03-29
*/
public class AnonymouslnnerClassDemo {
public static void main(String[] args) {
//方式1,直接new Thread重寫run方法
new Thread() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}.start();
//方式2,傳入一個Runnable()
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
}
}
//輸出結果
Thread-0
Thread-1
- Lambda表達式
/**
* Lambda
*
* @author venlenter
* @Description: 用lambda方式創建線程
* @since unknown, 2020-03-29
*/
public class Lambda {
public static void main(String[] args) {
new Thread(() -> System.out.println(Thread.currentThread().getName())).start();
}
}
3.1 總結:多線程的實現方式,在代碼中寫法千變萬化,但其本質萬變不離其宗。就是通過2種方式:繼承Thread類;實現Runnable接口
4、實現多線程常見面試問題
4.1、有多少種實現線程的方法?思路有5點
- 從不同的角度看,會有不同的答案
- 典型答案是2種(繼承Thread類、實現Runnable接口)
- 實際上,實現Runnable接口更好一點(有3點優點-見下方4.2)。
- Runnable方式,最終調用的是傳入的Runnable對象的run()方法
public class RunnableStyle implements Runnable {
@Override
public void run() {
System.out.println("用Runnable方法實現線程");
}
public static void main(String[] args) {
Thread thread = new Thread(new RunnableStyle());
//target.run(),target即new RunnableStyle
thread.start();
}
}
- 而繼承Thread類的方式,是重寫了當前Thread類的run()方法
- 但實際上,看原理,兩種本質都是一樣的
Thread.java
private Runnable target; //target是我們傳入的new RunnableStyle()
@Override
public void run() {
if (target != null) {
target.run();
}
}
實際上都是利用了Thread類的run方法。只不過一個是重寫了Thread類的run方法,一個是傳進來target對象,再去執行target的run方法
- 其實還有其他創建方式,如線程池、定時器等方式,但本質上還是使用了上面的2種方式
- 總的來說:本質只有一種,新建線程必須通過Thread類,但通常我們把它區分爲2種形式(一種是繼承Thread類,一種是實現Runnable接口),另外還有更多的表現形式(如線程池、定時器、匿名內部類、Lambda)
4.2、實現Runnable接口和繼承Thread類哪種方式更好
- 從代碼架構角度
- 這裏有2件事情,第一是具體的功能,即run方法;
- 第二是跟線程生命週期相關的(創建線程、運行線程),這個實際上是Thread類纔去做的事情,從代碼設計上來說,應該解耦出來,所以用Runnable接口的方式更好
- 新建線程的損耗
- 如果用Thread的方式,每次需要new一個Thread對象,新建一個線程,執行完還需要銷燬
- 如果用Runnable的方式,傳入實現Runnable的對象,就可以反覆使用這個線程,線程池就是這樣做的,這樣用於生命週期的損耗就減少了
- Java不支持雙繼承
筆記來源:慕課網悟空老師視頻《Java併發核心知識體系精講》