一、什麼是線程
進程是代碼在數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,線程則是進程的一個執行路徑,一個進程中至少有一個線程,進程中的多個線程共享進程的資源。
操作系統在分配資源時是把資源分配給進程的,但是CPU資源比較特殊,它是被分配到線程的,因爲真正要佔用CPU運行的是線程,所以也說線程是CPU分配的基本單位。
以Java 爲例,我們啓動一個main函數時,實際上就是啓動了一個JVM 的進程,main函數所在的線程就是這個進程的一個線程,也稱爲主線程。一個JVM進程中有多個線程,多個線程共享進程的堆和方法區資源,但是每個線程有自己的程序計數器和棧區域。
二、從內存角度看線程
內存是非常重要的系統資源,是硬盤和CPU的中間倉庫及橋樑,承載着操作系統和應用程序的實時運行。JVM 內存佈局規定了 Java 在運行過程中內存申請、分配、管理的策略 ,保證了 JVM 的高效穩定運行。
如果按照線程是否共享來分類的話,如下圖所示:
三、線程創建和運行
Thread類:
package java.lang;
public class Thread implements Runnable {
// 構造方法
public Thread(Runnable target);
public Thread(Runnable target, String name);
public synchronized void start();
}
Runnable 接口
package java.lang;
@FunctionalInterface
public interface Runnable {
pubic abstract void run();
}
3.1 繼承Thread類
其實現繼承關係圖爲:Thread 實現了Runnbale接口,並重寫了Run
其中target是一個Runnable對象,使用繼承的方式target爲null不會調用此方法
繼承的創建線程:
public class ThreadDemo01 extends Thread {
@Override
public void run() {
super.run();
System.out.println("MyThread");
}
public static void main(String[] args) {
ThreadDemo01 thread1 = new ThreadDemo01();
thread1.start();
}
}
我們自己的MyThread線程類繼承Thread,重寫run方法
3.2 繼承Thread匿名內部類的寫法
public class ThreadDemo2 {
public static void main(String[] args) {
// 初始化線程實例
new Thread() {
@Override
public void run() {
System.out.println(getName() + " is running");
}
}.start();
}
}
這種方式主要是Thread接口的構造函數的區分,當target不爲空的時候,會執行匿名內部類裏面的run方法
3.3 實現Runnable方法
基於兩個參數的構造函數:
public class CreateThreadDemo3{
public static void main(String[] args) {
RunnableThreadTest runnableThreadTest = new RunnableThreadTest();
new Thread(runnableThreadTest, "線程1").start();
new Thread(runnableThreadTest, "線程2").start();
}
}
/**
* 實現Runnable接口的方式
*/
class RunnableThreadTest implements Runnable{
private int i = 0;
@Override
public void run() {
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " is running: " + i);
}
}
}
3.4 實現Runable接口匿名內部類的方式
public class ThreadDemo2 {
public static void main(String[] args) {
// 初始化線程實例
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("匿名內部類...");
}
});
thread.start();
}
}
簡化java8的形式
public class ThreadDemo2 {
public static void main(String[] args) {
// 初始化線程實例
Thread thread = new Thread(() -> System.out.println("匿名內部類..."));
thread.start();
}
}
3.5 繼承Callable接口創建線程
和Runnable接口不一樣,Callable接口提供了一個call()方法作爲線程執行體,call()方法比run()方法功能要強大:call()方法可以有返回值,可以聲明拋出異常。
public class ThreadDemo5 {
public static void main(String[] args) {
CallableTest callableTest = new CallableTest();
FutureTask futureTask = new FutureTask<>(callableTest);
new Thread(futureTask).start();
try {
System.out.println("子線程返回值:" + futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class CallableTest implements Callable{
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
System.out.println(Thread.currentThread().getName() + "running: " + sum);
return sum;
}
}
Java5提供了Future接口來接收Callable接口中call()方法的返回值。
Callable接口是 Java5 新增的接口,不是Runnable接口的子接口,所以Callable對象不能直接作爲Thread對象的target。
針對這個問題,引入了RunnableFuture接口,RunnableFuture接口是Runnable接口和Future接口的子接口(下圖所示),可以作爲Thread對象的target (實現RunnableFuture接口的子類可以作爲Thread的target)。同時,Java5提供了一個RunnableFuture接口的實現類:FutureTask ,FutureTask可以作爲Thread對象的target。
看一下FutrueTask的實現,可以看出FutureTask即支持實現Callable接口的子類,也支持Runnable接口的子類,並且當傳過來的是Runnable接口的時候,會執行Executors的callable方法轉化爲callable接口的,執行的。
至於Future接口的原理,在後面的文章中再作分析,現在只是知道線程的創建方式。
四、創建線程的三種方式的對比
4.1 實現Runnable/Callable接口相比繼承Thread類的優勢
總體來說一般的業務代碼中都是採用實現Runnable接口或者是Callable接口
- 可以避免java中單繼承的限制
- 適合多個線程進行資源共享
- 線程池只能放入Runable或Callable接口實現類,不能直接放入繼承Thread的類
4.2 Callable和Runnable的區別
- Callable重寫的是call()方法,Runnable重寫的方法是run()方法
- call()方法執行後可以有返回值,run()方法沒有返回值
- call()方法可以拋出異常,run()方法不可以
- 運行Callable任務可以拿到一個Future對象,表示異步計算的結果 。通過Future對象可以瞭解任務執行情況,可取消任務的執行,還可獲取執行結果
參考文章
https://juejin.im/post/5d9ab5dae51d4578453274ba
https://juejin.im/post/5ab116875188255561411b8a#heading-29
想要獲取更多精選技術文章推薦的小夥伴們,請長按下圖,關注微信公衆號前後端精選,或者直接在微信上搜索“前後端精選”即可關注!