概要:
java建立線程的三種方式:
- 繼承Thread類建立線程類
- 實作Runnable接口
- 通過Callable和Future建立線程
java建立線程池的四種方式:
newCachedThreadPool 建立一個可緩存的線程池,如果線程池長度超過處理需求,可靈活回收空閑線程,若無可回收,則建立線程
newFixedThreadPool 建立一個定長線程池,可控制線程最大并發數,超出的線程會在隊列中等待
newScheduledThreadPool 建立一個定長線程池,支援定時及周期性任務執行
newSingleThreadExecutor 建立一個單線程化的線程池,它隻會唯一的工作線程來執行任務,保證所有任務按照指定
順序(FIFO,LIFO,優先級)執行
線程池的優點:
a. 重用存在的線程,減少對象建立、消亡的開銷,性能佳。
b. 可有效控制最大并發線程數,提高系統資源的使用率,同時避免過多資源競争,避免堵塞。
c. 提供定時執行、定期執行、單線程、并發數控制等功能。
第一 Java中建立線程主要有三種方式:
1、繼承Thread類建立線程類 (extends)
(1)定義Thread類的子類,并重寫該類的run方法,該run方法的方法體就代表了線程要完成的任務。是以把run()方法稱為執行體(線程體)。
(2)建立Thread子類的執行個體,即建立了線程對象。
(3)調用線程對象的start()方法來啟動該線程。
public class FirstThreadTest extends Thread{
int i = 0;
//重寫run方法,run方法的方法體就是現場執行體
public void run()
{
for(;i<100;i++){
log.info(getName()+" "+i);
}
}
public static void main(String[] args)
{
for(int i = 0;i< 100;i++)
{
log.info(Thread.currentThread().getName()+" : "+i);
if(i==20)
{
new FirstThreadTest().start();
new FirstThreadTest().start();
}
}
}
}
上述代碼中Thread.currentThread()方法傳回目前正在執行的線程對象。GetName()方法傳回調用該方法的線程的名字。
2、通過Runnable接口建立線程類
(1)定義runnable接口的實作類,并重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執行體。
(2)建立 Runnable實作類的執行個體,并以此執行個體作為Thread的target來建立Thread對象,該Thread對象才是真正的線程對象。
(3)調用線程對象的start()方法來啟動該線程。
public class RunnableThreadTest implements Runnable
{
private int i;
public void run()
{
for(i = 0;i <100;i++)
{
log.info(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args)
{
for(int i = 0;i < 100;i++)
{
log.info(Thread.currentThread().getName()+" "+i);
if(i==20)
{
RunnableThreadTest runner= new RunnableThreadTest();
new Thread(runner,"新線程1").start();
new Thread(runner,"新線程2").start();
}
}
}
}
線程的執行流程很簡單,當執行代碼start()時,就會執行對象中重寫的void run()方法,該方法執行完成後,線程就消亡了。
3、通過Callable和Future建立線程
(1)建立Callable接口的實作類,并實作call()方法,該call()方法将作為線程執行體,并且有傳回值。
public interface Callable
{
V call() throws Exception;
}
(2)建立Callable實作類的執行個體,使用FutureTask類來包裝Callable對象,該FutureTask對象封裝了該Callable對象的call()方
法的傳回值。(FutureTask是一個包裝器,它通過接受Callable來建立,它同時實作了Future和Runnable接口。)
(3)使用FutureTask對象作為Thread對象的target建立并啟動新線程。
(4)調用FutureTask對象的get()方法來獲得子線程執行結束後的傳回值
public class CallableThreadTest implements Callable<Integer>
{
public static void main(String[] args)
{
CallableThreadTest ctt = new CallableThreadTest();
FutureTask<Integer> ft = new FutureTask<>(ctt);
for(int i = 0;i < 100;i++)
{
log.info(Thread.currentThread().getName()+" 的循環變量i的值"+i);
if(i==20)
{
new Thread(ft,"有傳回值的線程").start();
}
}
try
{
log.info("子線程的傳回值:"+ft.get());
} catch (InterruptedException e)
{
e.printStackTrace();
} catch (ExecutionException e)
{
e.printStackTrace();
}
}
@Override
public Integer call() throws Exception
{
int i = 0;
for(;i<100;i++)
{
log.info(Thread.currentThread().getName()+" "+i);
}
return i;
}
}
二、建立線程的三種方式的對比
1、采用實作Runnable、Callable接口的方式建立多線程
優勢:
線程類隻是實作了Runnable接口或Callable接口,還可以繼承其他類。
在這種方式下,多個線程可以共享同一個target對象,是以非常适合多個相同線程來處理同一份資源的情況,進而可以将CPU、代碼和資料分開,形成清晰的模型,較好地展現了面向對象的思想。
劣勢:
程式設計稍微複雜,如果要通路目前線程,則必須使用Thread.currentThread()方法。
2、使用繼承Thread類的方式建立多線程
優勢:
編寫簡單,如果需要通路目前線程,則無需使用Thread.currentThread()方法,直接使用this即可獲得目前線程。
劣勢:
線程類已經繼承了Thread類,是以不能再繼承其他父類。
3、Runnable和Callable的差別
(1) Callable規定(重寫)的方法是call(),Runnable規定(重寫)的方法是run()。
(2) Callable的任務執行後可傳回值,而Runnable的任務是不能傳回值的。
(3) call方法可以抛出異常,run方法不可以。
(4) 運作Callable任務可以拿到一個Future對象,表示異步計算的結果。它提供了檢查計算是否完成的方法,以等待計算的
完成,并檢索計算的結果。通過Future對象可以了解任務執行情況,可取消任務的執行,還可擷取執行結果future.get()。
第二 建立四種線程池的方式
1、new Thread的弊端
執行一個異步任務你還隻是如下new Thread嗎?
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
}
}
).start();
那你就out太多了,new Thread的弊端如下:
a. 每次new Thread建立對象性能差。
b. 線程缺乏統一管理,可能無限制建立線程,互相之間競争,及可能占用過多系統資源導緻當機或oom(out of memory)。
c. 缺乏更多功能,如定時執行、定期執行、線程中斷。
相比new Thread,Java提供的四種線程池的好處在于:
a. 重用存在的線程,減少對象建立、消亡的開銷,性能佳。
b. 可有效控制最大并發線程數,提高系統資源的使用率,同時避免過多資源競争,避免堵塞。
c. 提供定時執行、定期執行、單線程、并發數控制等功能。
2、Java 線程池
Java通過Executors提供四種線程池,分别為:
newCachedThreadPool建立一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則建立線程。
newFixedThreadPool 建立一個定長線程池,可控制線程最大并發數,超出的線程會在隊列中等待。
newScheduledThreadPool 建立一個定長線程池,支援定時及周期性任務執行。
newSingleThreadExecutor 建立一個單線程化的線程池,它隻會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。
(1) newCachedThreadPool:
建立一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閑線程,若無可回收,則建立線程。示例代碼如下:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(index * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
log.info(index);
}
});
}
線程池為無限大,當執行第二個任務時第一個任務已經完成,會複用執行第一個任務的線程,而不用每次建立線程。
(2) newFixedThreadPool:--- 需要指定線程池的大小
建立一個定長線程池,可控制線程最大并發數,超出的線程會在隊列中等待。示例代碼如下:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
try {
log.info(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
因為線程池大小為3,每個任務輸出index後sleep 2秒,是以每兩秒列印3個數字。
定長線程池的大小最好根據系統資源進行設定。如Runtime.getRuntime().availableProcessors()。可參考PreloadDataCache。
(3)newScheduledThreadPool:
建立一個定長線程池,支援定時及周期性任務執行。延遲執行示例代碼如下:
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {
@Override
public void run() {
log.info("delay 3 seconds");
}
}, 3, TimeUnit.SECONDS);
表示延遲3秒執行。
定期執行示例代碼如下:
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
log.info("delay 1 seconds, and excute every 3 seconds");
}
}, 1, 3, TimeUnit.SECONDS);
表示延遲1秒後每3秒執行一次。
ScheduledExecutorService比Timer更安全,功能更強大
(4)newSingleThreadExecutor:
建立一個單線程化的線程池,它隻會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。示例代碼如下:
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
@Override
public void run() {
try {
log.info(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
}
結果依次輸出,相當于順序執行各個任務。
線程池的作用:
線程池作用就是限制系統中執行線程的數量。
根據系統的環境情況,可以自動或手動設定線程數量,達到運作的最佳效果;少了浪費了系統資源,多了造成系統擁擠效率
不高。用線程池控制線程數量,其他線程排隊等候。一個任務執行完畢,再從隊列的中取最前面的任務開始執行。若隊列中沒
有等待程序,線程池的這一資源處于等待。當一個新任務需要運作時,如果線程池中有等待的工作線程,就可以開始運作了;
否則進入等待隊列。
為什麼要用線程池:
1.減少了建立和銷毀線程的次數,每個工作線程都可以被重複利用,可執行多個任務。
2.可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因為消耗過多的記憶體,而把伺服器累趴下(每個線程需要大
約1MB記憶體,線程開的越多,消耗的記憶體也就越大,最後當機)。
Java裡面線程池的頂級接口是Executor,但是嚴格意義上講Executor并不是一個線程池,而隻是一個執行線程的工具。真正的
線程池接口是ExecutorService。
比較重要的幾個類:
ExecutorService: 真正的線程池接口。
ScheduledExecutorService: 能和Timer/TimerTask類似,解決那些需要任務重複執行的問題。
ThreadPoolExecutor: ExecutorService的預設實作。
ScheduledThreadPoolExecutor: 繼承ThreadPoolExecutor的ScheduledExecutorService接口實作,周期性任務排程的類實作。
要配置一個線程池是比較複雜的,尤其是對于線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優的,是以
在Executors類裡面提供了一些靜态工廠,生成一些常用的線程池。