好久沒寫筆記了,重新回歸Java,打好基礎。
Java 進程中每一個線程都對應著一個 Thread 實例,其中保存著線程的描述信息。
Thread 類
Java 使用 Thread 類表示線程,首先看一個簡單的示例。
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread();
System.out.println("線程名稱: " + thread.getName());
System.out.println("線程ID: " + thread.getId());
System.out.println("線程狀態(tài): " + thread.getState());
System.out.println("線程優(yōu)先級: " + thread.getPriority());
thread.start();
}
運行示例,可以看到如下輸出:

Thread 類中比較重要的屬性包括:
-
tid,線程ID,通過getId()方法可以獲取線程ID,線程 ID 在進程中是唯一的。 -
name,線程名稱,通過getName()方法可以獲取線程名稱,通過setName(String name)方法可以設置線程名稱。 -
priority, 線程優(yōu)先級,通過getPriority()方法可以獲取線程優(yōu)先級,通過setPriority(int priority)方法可以設置線程優(yōu)先級。Java線程最小值為1,最大值為10,默認為5。 -
daemon,標記線程是否為守護線程,setDaemon(boolean on)方法可以設置線程是否為守護線程。默認值為false,表示為普通的用戶線程,不是守護線程。 -
threadState,線程狀態(tài),以整數形式表示,通過getState()方法可以返回當前線程的狀態(tài)。返回值為如下枚舉:
public static enum State {
NEW, // 新建
RUNNABLE, // 就緒、運行
BLOCKED, // 阻塞
WAITING, // 等待
TIMED_WAITING, // 休時等得
TERMINATED; // 結束
}
Thread 類中還有幾個常用的方法:
-
start()方法,用來啟動一個線程。 -
run()方法,作為線程代碼邏輯的入口方法。當調用start()方法啟動一個線程后,只要線程獲取了 CPU 執(zhí)行時間,就會進入run()方法執(zhí)行用戶代碼。
另外,通過靜態(tài)方法 currentThread() 可以獲取當前線程的實例對象。
通過繼承 Thread 類創(chuàng)建線程
通過繼承 Thread 類創(chuàng)建線程包含兩個步驟:
- 編寫一個繼承
Thread類的新線程類。 - 重寫
run()方法,將要執(zhí)行的業(yè)務代碼添加到其中。
下面看一個簡單的示例:
public class createThread1 {
static int threadNo = 1;
static class MyThread extends Thread {
public MyThread() {
super("MyThread-" + threadNo);
threadNo++;
}
public void run() {
for(int i = 0; i< 3; i++) {
System.out.println(getName() + ",輪次: " + i);
}
System.out.println(getName() + "運行結束.");
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = null;
for(int i = 0; i < 2; i++) {
thread = new MyThread();
thread.start();
}
}
}
運行代碼,可以得到輸出:

通過實現(xiàn) Runnable 接口創(chuàng)建線程
如果看下 Thread 類的源碼,我們會發(fā)現(xiàn)它實現(xiàn)了一個接口 Runnable。Runnable 源碼如下,其中只包含一個抽象方法 run()。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Thread 類中包含著一個擁有 Runnable 參數的構造器,利用這個構造器我們就可以生成 Thread 類。
public Thread(Runnable target)
public Thread(Runnable target, String name)
我們可以通過實現(xiàn) Runnable 接口來創(chuàng)建一個線程。具體步驟如下:
- 定義一個實現(xiàn)了
Runnable接口的類。 - 實現(xiàn)
Runnable接口的run()方法,將代碼邏輯放入其中。 - 通過
Thread類創(chuàng)建線程對象,將現(xiàn)了Runnable接口的類實例傳入其中。
看一個例子:
public class createThread2 {
static int threadNo = 1;
static class RunTarget implements Runnable {
public void run() {
for(int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + ", 輪次: " + i);
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = null;
for(int i = 0; i < 2; i++) {
Runnable target = new RunTarget();
thread = new Thread(target, "MyRunnableThread-" + threadNo);
threadNo++;
thread.start();
}
}
}
運行代碼,輸出如下:

我們還可以利用匿名類和 lambda 表達式,更加優(yōu)雅的使用 Runnbale 接口創(chuàng)建線程。
將上面的例子改造成匿名類:
public class createThread2 {
static int threadNo = 1;
public static void main(String[] args) throws InterruptedException {
Thread thread = null;
for(int i = 0; i < 2; i++) {
thread = new Thread(new Runnable() {
@Override
public void run() {
for(int j = 0; j < 3; j++) {
System.out.println(Thread.currentThread().getName() + ", 輪次:" + j);
}
}
}, "MyRunnableThread-" + threadNo);
threadNo++;
thread.start();
}
}
}
將上面的例子改造成 Lambda 表達式:
public class createThread2 {
static int threadNo = 1;
public static void main(String[] args) throws InterruptedException {
Thread thread = null;
for(int i = 0; i < 2; i++) {
thread = new Thread(() -> {
for(int j = 0; j < 3; j++) {
System.out.println(Thread.currentThread().getName() + ", 輪次:" + j);
}
}, "MyRunnableThread-" + threadNo);
threadNo++;
thread.start();
}
}
}
使用 Runnable 接口創(chuàng)建現(xiàn)成的方式優(yōu)點是可以更好地分離邏輯和數據,多個線程能夠并行處理同一個資源。
通過 Callable 和 FutureTask 創(chuàng)建線程
通過繼承 Thread 類或者實現(xiàn) Runnable 接口創(chuàng)建線程的缺陷是無法獲取異步結果。
如果要使用異步執(zhí)行,那么就要用 Callable 接口和 FutureTask 類結合創(chuàng)建線程。
Callable 接口
Callable 接口源碼如下,它是一個泛型接口,也是一個函數式接口。其唯一的抽象方法 call() 有返回值,該方法還有一個 Exception 的異常聲明,允許方法的實現(xiàn)版本的內部異常直接拋出,并且允許不予捕獲。
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
RunnableFuture 接口
RunnableFuture 接口的源碼如下,它也是一個泛型接口,并且繼承了 Runnable 和 Future 接口,這樣它一方面可以作為 Thread 線程實例的 target 實例,另一方面可以異步執(zhí)行。
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
下面看一下為什么繼承了 Future 接口就可以異步執(zhí)行了。
Future 接口
Future 接口是用來處理異步任務的,它主要提供了三大功能:
- 能夠取消異步執(zhí)行中的任務。
- 判斷異步任務是否執(zhí)行完成。
- 獲取異步任務完成后的執(zhí)行結果。
public interface Future<V> {
boolean cancel(boolean mayInterruptRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
各個方法說明:
-
V get():獲取異步任務執(zhí)行的結果。注意,這個方法的調用是阻塞性的,如果異步任務沒有執(zhí)行完成,異步結果獲取線程會一直阻塞,直到異步任務執(zhí)行完成,將結果返回給調用線程。 -
V get(long timeout, TimeUnit unit):設置時限并獲取異步任務執(zhí)行的結果,如果阻塞時間超過設定的時限,該方法就拋出異常。 -
boolean isDone():獲取異步任務是否執(zhí)行完成,執(zhí)行完成則返回true。 -
boolean isCancelled():獲取異步任務是否取消,任務完成前取消則返回true。 -
boolean cancel(boolean mayInterruptRunning):取消異步任務的執(zhí)行。
FutureTask 類
Future 只是個接口,而 FutureTask 類是它的一個默認實現(xiàn),更準確的說, FutureTask 類實現(xiàn)了 RunnableTask 接口。
FutureTask 類中有一個 Callable 類型的成員,用來保存任務。FutureTask 的 run() 方法會執(zhí)行 call() 方法。
創(chuàng)建線程
步驟如下:
- 創(chuàng)建一個
Callable接口的實現(xiàn)類,并實現(xiàn)其call()方法,在其中編寫異步執(zhí)行邏輯,可以有返回值。 - 使用
Callable實現(xiàn)類的實例構造一個FutureTask實例。 - 使用
FutureTask實例作為Thread構造器target的入參,構造Thread線程實例。
這樣線程就構造好了,線程啟動后,我們還可以調用 FutureTask 對象的 get() 方法阻塞性地獲得并發(fā)線程的執(zhí)行結果。
public class createThread3 {
static class MyTask implements Callable<Long> {
public Long call() throws Exception {
long startTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + " 線程開始運行.");
Thread.sleep(500);
for(int i = 0; i < 100000000; i++) {
int j = i * 10000;
}
long used = System.currentTimeMillis() - startTime;
System.out.println(Thread.currentThread().getName() + " 線程結束運行.");
return used;
}
}
public static void main(String[] args) throws InterruptedException {
MyTask task = new MyTask();
FutureTask<Long> futureTask = new FutureTask<>(task);
Thread thread = new Thread(futureTask, "MyThread");
thread.start();
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + " 先歇一會.");
for(int i = 0; i < 100000000 / 2; i++) {
int j = i * 10000;
}
System.out.println(Thread.currentThread().getName() + " 獲取并發(fā)任務的執(zhí)行結果.");
try {
System.out.println(thread.getName() + " 線程占用時間: " + futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 運行結束.");
}
}
通過 FutureTask 的 get 方法獲取結果,有 2 種情況:
- 異步任務執(zhí)行完成,直接返回結果。
- 異步任務沒有執(zhí)行完成,一直阻塞到執(zhí)行完成返回結果。
通過線程池創(chuàng)建線程
前面創(chuàng)建的線程在使用完之后就銷毀了,比較浪費時間和資源,為了不頻繁創(chuàng)建和銷毀線程,需要對線程進行復用,這時候就要用到線程池技術。
Java 種提供了一個靜態(tài)工廠 Executors 類來創(chuàng)建不同的線程池。簡單示例如下:
private static ExecutorService pool = Executors.newFixedThreadPool(3);
ExecutorService 是 Java 提供的線程池接口,可以通過它的實例提交或者執(zhí)行任務。ExecutorService 實例負責對池中的線程進行管理和調度,并且可以控制最大并發(fā)線程數,同時提供了定時執(zhí)行、定頻執(zhí)行、單線程、并發(fā)數控制等功能。
向 ExecutorService 線程池提交異步執(zhí)行 target 目標任務的常用方法:
// 執(zhí)行一個 Runnable 類型的目標實例,無返回
void execute(Runnable command);
// 提交一個 Callable 類型的目標實例,返回一個 Future 異步任務實例
<T> Future<T> submit(Callable<T> task);
// 提交一個 Runnable 類型的目標實例,返回一個 Future 異步任務實例
Future<?> submit(Runnable task);
下面看一個實戰(zhàn)例子。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class createThread4 {
private static ExecutorService pool = Executors.newFixedThreadPool(3);
static class MyThread implements Runnable {
@Override
public void run() {
for(int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ", 輪次: " + i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class MyTask implements Callable<Long> {
public Long call() throws Exception {
long startTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + " 線程開始運行.");
Thread.sleep(500);
for(int i = 0; i < 100000000; i++) {
int j = i * 10000;
}
long used = System.currentTimeMillis() - startTime;
System.out.println(Thread.currentThread().getName() + " 線程結束運行.");
return used;
}
}
public static void main(String[] args) throws Exception{
pool.execute(new MyThread());
pool.execute(new Runnable() {
@Override
public void run() {
for(int j = 0; j < 5; j++) {
System.out.println(Thread.currentThread().getName() + ", 輪次: " + j);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Future future = pool.submit(new MyTask());
Long result = (Long)future.get();
System.out.println("異步任務的執(zhí)行結果為: " + result);
}
}
運行結果如下:

可以看到線程池中的線程默認名稱和普通線程也有所不同。
注意其中 ExecutorService 線程池的 execute 和 submit 方法有如下區(qū)別:
接收參數不一樣。
submit方法可以接收無返回值的Runnable類型和有返回值的Callable類型,execute僅接收無返回值的Runnable類型或者Thread實例。submit方法有返回值,而execute方法沒有。