前言
在java中一個線程是使用Thread的執行個體來表示,所有線程對象都必須是Thread的子類執行個體或是Thread的執行個體,它位于java.lang包中,是Java中非常常用也是非常重要的一個基礎類。Thread類實作了Runnable接口,它有不少非常重要的方法和屬性:
那麼建立Java的線程一般有哪幾種方式呢?
一、繼承Thread類建立線程類
1. 定義Thread類的子類ThreadDemo,并重寫該Thread類的run()方法,在run()方法中執行該線程的主要邏輯。
2. 在主線程上建立Thread子類的執行個體,即建立了一個線程對象。
3. 調用線程對象的start()方法來啟動該線程。
// 繼承Thread類,建立一個新的線程類
public class ThreadDemo extends Thread{
// 重寫了Thread類的run()方法,将需要并發執行的使用者業務代碼編寫在繼承的run()方法中
@Overrite
public void run(){
System.out.println(getName()+" 這裡執行線程的業務邏輯");
}
}
public class AppDemo {
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
// 啟動線程
threadDemo.start();
System.out.println("線程名:"+Thread.currentThread().getName()+" 運作結束");
}
}
執行結果:
線程名:main 運作結束
Thread-0 這裡執行線程的業務邏輯
二、實作Runnable接口,建立線程目标類
有了Thread類了,為什麼還需要多一個Runnable的接口方式來建立一個線程呢?那是因為Java繼承的一個特征,extend隻能一個父類,當一個子類已經繼承了一個父類時,此時你希望這個類成為一個線程類,是不行的,但它能實作Runnable接口。
1.定義Runnable接口的實作類,并實作run()方法,在run()方法中執行該線程的主要邏輯。
⒉.建立Runnable實作類的執行個體,并将其作為Thread的target來建立Thread對象,Thread對象為線程對象。
3.調用線程對象的start()方法來啟動該線程。
public class RunnableThreadDemo implements Runnable {
// 重寫了Runnable類的run()方法,将需要并發執行的使用者業務代碼編寫在繼承的run()方法中
@0verrite
public void run( ){
System.out.println(Thread.currentThread().getName()+" 這裡執行線程的業務邏輯" )
}
}
public class AppDemo {
public static void main(String[] args) {
RunnableThreadDemo threadDemo = new RunnableThreadDemo();
// 啟動線程
threadDemo.start();
System.out.println("線程名:"+Thread.currentThread().getName()+" 運作結束");
}
}
執行結果:
線程名:main 運作結束
Thread-0 這裡執行線程的業務邏輯
三、使用匿名内部類,實作建立Thread子類
跟方式一類似,隻是不需要建立一個單獨的類檔案來繼承Thread,而是使用了匿名類來完成,代碼如下:
public class AppDemo {
public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
System.out.println(getName()+" 這裡執行線程的業務邏輯");
}
};
thread.start();
System.out.println("線程名:"+Thread.currentThread().getName()+" 運作結束");
}
}
執行結果:
線程名:main 運作結束
Thread-0 這裡執行線程的業務邏輯
四、使用匿名内部類,實作Runnable接口
跟方式二類似,差別在于使用匿名類來完成,代碼如下:
public class AppDemo {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 這裡執行線程的業務邏輯" );
}
});
thread.start();
System.out.println("線程名:"+Thread.currentThread().getName()+" 運作結束");
}
}
執行結果:
Thread-0 這裡執行線程的業務邏輯
線程名:main 運作結束
什麼?這跟方式二執行的結果不一樣?這裡是沒問題的,這裡是兩個線程在跑着,哪個線程先跑是跟cpu的排程有關,跟代碼的先後無關。這點請緊記。
五、使用lambda表達式來完成
使用lambda表達式使得代碼更簡潔,實作其實跟方式三一樣,隻是使用lambda來代替匿名類。
public class AppDemo {
public static void main(String[] args) {
Thread thread = new Thread(()->{
System.out.println(Thread.currentThread().getName()+" 這裡執行線程的業務邏輯");
});
thread.start();
System.out.println("線程名:"+Thread.currentThread().getName()+" 運作結束");
}
}
執行結果:
線程名:main 運作結束
Thread-0 這裡執行線程的業務邏輯
六、實作Callable接口
繼承Thread類或者實作Runnable接口這兩種方式來建立線程類有一個缺陷,就是不能擷取異步執行的結果,即在主線程啟動子線程後,能run起來就分道揚镳了,但在很多場景上需要擷取到異步執行的結果,這個時候就需要實作Callable接口了。這是在1.5之後提供的一種建立多線程的方法,通過Callable接口和FutureTask類互相作用來完成線程執行以及在主線程中擷取執行的結果。
Callable接口位于java.util.concurrent包中:
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
建立步驟如下:
- 首先定義一個 Callable 的實作類,并實作call方法。
- call方法是帶傳回值的。然後通過FutureTask的構造方法,把這個Callable 實作類傳進去。
- 把 FutureTask作為 Thread 類的 target ,建立Thread線程對象。
- 通過FutureTask 的get方法擷取線程的執行結果。
public class CallableTaskDemo implements Callable {
// 編寫好異步執行的具體邏輯,可以有傳回值。
// (Runnable接口中的run()方法是沒有傳回值得,Callable接口的call()方法有傳回值)
@Override
public Long call() throws Exception {
Long startTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName()+" 線程開始運作");
Thread.sleep(1000);
for(int i=0;i<100000000;i++){
int j = i*10000;
}
Long endTime = System.currentTimeMillis();
Long used = endTime-startTime;
System.out.println(Thread.currentThread().getName()+" 線程結束運作");
return used;
}
}
public class AppDemo {
public static void main(String[] args) throws InterruptedException {
CallableTaskDemo callableTaskDemo = new CallableTaskDemo();
FutureTask<Long> futureTask = new FutureTask<Long>(callableTaskDemo);
Thread thread = new Thread(futureTask,"callableTaskThread");
thread.start();
Thread.sleep(500);
System.out.println("main線程執行一會");
for(int i=0;i<100000000/2;i++){
int j = i*10000;
}
// 擷取并發任務的執行結果
try {
System.out.println(thread.getName()+" 線程占用時間:"+futureTask.get());
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
執行結果:
callableTaskThread 線程開始運作
main線程執行一會
callableTaskThread 線程結束運作
callableTaskThread 線程占用時間:1008
七、使用自帶線程池
使用線程池的一個很重要的原因是線程的複用性。我們知道線程資源在伺服器上來看,是一種非常昂貴的資源,如果使用不當,伺服器的負載是非常高,并且可能會出現超負荷的情況,往往會導緻伺服器當機等問題,是以線程的合理配置設定就變得是一個非常有意思的話題。線程池往往就能很好地解決這個問題,設定一個線程池,往線程池中注入固定的線程數,超過的線程的任務數,要不等待,要不抛棄。大大地複用了線程,減少線程頻繁建立與銷毀,提高了伺服器的可用率。
Java線上程池上也有多種類型的支援,在不同的場景下可選擇不同類型的線程池。
-
FixThreadPool(int n);--固定大小的線程池
使用于負載比較重的伺服器,滿足資源管理需求而對資源有強制限制的需求
-
SingleThreadPoolExecutor --單線程池
需要保證順序執行各個任務的場景,任務按先進先出的形式來執行
-
CashedThreadPool() -- 緩存線程池
适用于送出短期的異步小程式以及負載較輕的伺服器。當送出任務速度高于線程池中任務處理速度時,緩存線程池會不斷地建立線程。調用execute 将重用以前構造的線程(如果線程可用)。如果現有線程沒有可用的,則建立一個新線程并添加到池中。終止并從緩存中移除那些已有 60 秒鐘未被使用的線程。
-
ScheduledExecutor --定時及周期性的任務執行的線程池
适用于延遲或排程類的工作,多數情況可替代Timer類
一般的使用方式如下:
- 首先,定義一個 Runnable 的實作類,重寫run方法
- 然後建立一個擁有固定線程數的線程池
- 最後通過ExecutorService對象的execute 方法傳入線程對象
public class RunnableTaskDemo implements Runnable {
@Override
public void run() {
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()+" 輪次:"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class CallableTaskDemo implements Callable {
@Override
public Long call() throws Exception {
Long startTime = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName()+" 線程開始運作");
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()+" 輪次:"+i);
Thread.sleep(1000);
}
Long used = System.currentTimeMillis()-startTime;
System.out.println(Thread.currentThread().getName()+" 線程結束運作");
return used;
}
}
public class AppDemo {
private static ExecutorService threadpool = Executors.newFixedThreadPool(3);
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 1、執行Runnbale類型的target目标執行個體,無傳回
threadpool.execute(new RunnableTaskDemo());
//2、 執行Runnbale類型的target目标執行個體,無傳回,内部類形式
threadpool.execute(new Runnable() {
@Override
public void run() {
for(int i=0;i<3;i++){
System.out.println(Thread.currentThread().getName()+" 輪次:"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
// 3、送出Callable執行目标執行個體,有傳回
Future future = threadpool.submit(new CallableTaskDemo());
System.out.println("異步執行的結果為:" + future.get());
}
}
執行結果:
pool-1-thread-1 輪次:0
pool-1-thread-2 輪次:0
pool-1-thread-3 線程開始運作
pool-1-thread-3 輪次:0
pool-1-thread-2 輪次:1
pool-1-thread-1 輪次:1
pool-1-thread-3 輪次:1
pool-1-thread-2 輪次:2
pool-1-thread-1 輪次:2
pool-1-thread-3 輪次:2
pool-1-thread-3 線程結束運作
異步執行的結果為:3002