技術(shù)背景
在面向?qū)ο缶幊讨校瑒?chuàng)建和銷毀對象是很費時間的棍丐,因為創(chuàng)建一個對象要獲取內(nèi)存資源或者其它更多資源误辑。在Java中更是如此,虛擬機將試圖跟蹤每一個對象歌逢,以便能夠在對象銷毀后進行垃圾回收巾钉。所以提高服務(wù)程序效率的一個手段就是盡可能減少創(chuàng)建和銷毀對象的次數(shù),特別是一些很耗資源的對象創(chuàng)建和銷毀秘案。如何利用已有對象來服務(wù)就是一個需要解決的關(guān)鍵問題砰苍,其實這就是一些"池化資源"技術(shù)產(chǎn)生的原因。比如大家所熟悉的數(shù)據(jù)庫連接池正是遵循這一思想而產(chǎn)生的阱高,本文將介紹的線程池技術(shù)同樣符合這一思想赚导。
--------------以上內(nèi)容來自百度百科-----------------------
java提供的幾種線程池
先介紹幾個類
Executors
提供一系列靜態(tài)方法用于創(chuàng)建線程池,返回的線程池對象都繼承了ExecutorService接口
ExecutorService
線程池對象赤惊,用于管理線程池中的線程
ScheduledExecutorService
具有定時效果的線程池對象
線程池介紹
newCachedThreadPool()
創(chuàng)建一個彈性線程池吼旧,自動回收不使用的線程(終止并從緩存中移除那些已有 60 秒鐘未被使用的線程),(在無可用線程的情況下)自動的為新來的task創(chuàng)建新線程荐捻。
newSingleThreadExecutor()
創(chuàng)建一個只有一個線程的線程池黍少,一個任務(wù)執(zhí)行完畢,下一個任務(wù)才能接著執(zhí)行
newFixedThreadPool(int nThreads)
創(chuàng)建一個固定線程數(shù)量的線程池处面,每次提交一個任務(wù)就創(chuàng)建一個線程厂置,直到線程達(dá)到線程池的最大大小。
newScheduledThreadPool(int corePoolSize)
創(chuàng)建一個定長線程池魂角,支持定時及周期性任務(wù)執(zhí)行昵济。注意corePoolSize,當(dāng)需要的線程超過corePoolSize時,還是會新建線程访忿,只不過超過的部分會在空閑時間回收
代碼示例
CachedThreadPool代碼示例
public class ThreadPool_CacheDemo {
public static void main(String[] args) {
ExecutorService es1 = Executors.newCachedThreadPool();
es1.execute(new MyTask("任務(wù)1"));
es1.execute(new MyTask("任務(wù)2"));
es1.execute(new MyTask("任務(wù)3"));
es1.execute(new MyTask("任務(wù)4"));
es1.execute(new MyTask("任務(wù)5"));
}
}
class MyTask implements Runnable {
private String taskName;
public MyTask(String taskName){
this.taskName=taskName;
}
public void run() {
System.out.println(taskName+"執(zhí)行時間:"+System.currentTimeMillis());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
運行結(jié)果
任務(wù)4執(zhí)行時間:1507874348487 線程名:pool-1-thread-4
任務(wù)3執(zhí)行時間:1507874348487 線程名:pool-1-thread-3
任務(wù)1執(zhí)行時間:1507874348487 線程名:pool-1-thread-1
任務(wù)5執(zhí)行時間:1507874348487 線程名:pool-1-thread-5
任務(wù)2執(zhí)行時間:1507874348487 線程名:pool-1-thread-2
可以看出5個任務(wù)幾乎是同一時間執(zhí)行的瞧栗,并且每個任務(wù)都對應(yīng)一個線程
SingleThreadPool代碼示例
public class ThreadPool_SingleDemo {
public static void main(String[] args) {
ExecutorService es2 = Executors.newSingleThreadExecutor();
es2.execute(new MyTask("任務(wù)1"));
es2.execute(new MyTask("任務(wù)2"));
es2.execute(new MyTask("任務(wù)3"));
es2.execute(new MyTask("任務(wù)4"));
es2.execute(new MyTask("任務(wù)5"));
}
}
運行結(jié)果
任務(wù)1執(zhí)行時間:1507874414908 線程名:pool-1-thread-1
任務(wù)2執(zhí)行時間:1507874416909 線程名:pool-1-thread-1
任務(wù)3執(zhí)行時間:1507874418910 線程名:pool-1-thread-1
任務(wù)4執(zhí)行時間:1507874420912 線程名:pool-1-thread-1
任務(wù)5執(zhí)行時間:1507874422913 線程名:pool-1-thread-1
可以看出每個任務(wù)和上個任務(wù)執(zhí)行時間相差差不多2秒鐘,并且只有一個線程
FixedThreadPool代碼示例
public class ThreadPool_FixedDemo {
public static void main(String[] args) {
ExecutorService es3 = Executors.newFixedThreadPool(2);
es3.execute(new MyTask("任務(wù)1"));
es3.execute(new MyTask("任務(wù)2"));
es3.execute(new MyTask("任務(wù)3"));
es3.execute(new MyTask("任務(wù)4"));
es3.execute(new MyTask("任務(wù)5"));
}
}
運行結(jié)果
任務(wù)2執(zhí)行時間:1507874489559 線程名:pool-1-thread-2
任務(wù)1執(zhí)行時間:1507874489559 線程名:pool-1-thread-1
任務(wù)3執(zhí)行時間:1507874491565 線程名:pool-1-thread-2
任務(wù)4執(zhí)行時間:1507874491565 線程名:pool-1-thread-1
任務(wù)5執(zhí)行時間:1507874493569 線程名:pool-1-thread-2
可以看出海铆,每兩個任務(wù)的執(zhí)行時間是相同的迹恐,并且只有兩個線程
ScheduledThreadPool代碼示例
注意,ScheduledThreadPool也可以調(diào)用execute方法卧斟,當(dāng)然如果調(diào)用這個方法就和前面介紹的FixedThreadPool沒什么區(qū)別了
public class ThreadPool_ScheduledDemo {
public static void main(String[] args) {
ScheduledExecutorService ses = Executors.newScheduledThreadPool(2);
System.out.println("線程池任務(wù)schedule時間:" + System.currentTimeMillis());
ses.schedule(new MyTask("任務(wù)1"), 2, TimeUnit.SECONDS);
ses.scheduleAtFixedRate(new MyTask("任務(wù)2"), 2, 3, TimeUnit.SECONDS);
ses.scheduleWithFixedDelay(new MyTask("任務(wù)3"), 1, 3, TimeUnit.SECONDS);
}
}
可以看到上述程序有3種執(zhí)行任務(wù)的方法,下面將一一介紹
ses.schedule(new MyTask("任務(wù)1"), 2, TimeUnit.SECONDS);
運行結(jié)果
線程池任務(wù)schedule時間:1507876230078
任務(wù)1執(zhí)行時間:1507876232081 線程名:pool-1-thread-1
可以看出任務(wù)1在延遲了2秒后才去執(zhí)行任務(wù)
ses.scheduleAtFixedRate(new MyTask("任務(wù)2"), 2, 3, TimeUnit.SECONDS);
運行結(jié)果
線程池任務(wù)schedule時間:1507876503620
任務(wù)2執(zhí)行時間:1507876505627 線程名:pool-1-thread-1
任務(wù)2執(zhí)行時間:1507876508622 線程名:pool-1-thread-1
任務(wù)2執(zhí)行時間:1507876511627 線程名:pool-1-thread-1
任務(wù)2執(zhí)行時間:1507876514627 線程名:pool-1-thread-1
任務(wù)2執(zhí)行時間:1507876517627 線程名:pool-1-thread-1
任務(wù)2執(zhí)行時間:1507876520627 線程名:pool-1-thread-1
任務(wù)2執(zhí)行時間:1507876523627 線程名:pool-1-thread-1
任務(wù)2執(zhí)行時間:1507876526627 線程名:pool-1-thread-1
可以看出在延遲2秒后锤岸,任務(wù)2在周期性的執(zhí)行著是偷,從時間上看這個周期剛好是3秒
ses.scheduleWithFixedDelay(new MyTask("任務(wù)3"), 1, 3, TimeUnit.SECONDS);
運行結(jié)果
線程池任務(wù)schedule時間:1507876760966
任務(wù)3執(zhí)行時間:1507876761969 線程名:pool-1-thread-1
任務(wù)3執(zhí)行時間:1507876766972 線程名:pool-1-thread-1
任務(wù)3執(zhí)行時間:1507876771974 線程名:pool-1-thread-1
任務(wù)3執(zhí)行時間:1507876776979 線程名:pool-1-thread-1
任務(wù)3執(zhí)行時間:1507876781985 線程名:pool-1-thread-1
任務(wù)3執(zhí)行時間:1507876786990 線程名:pool-1-thread-1
任務(wù)3執(zhí)行時間:1507876792000 線程名:pool-1-thread-1
可以看出在延遲1秒后蛋铆,任務(wù)2在周期性的執(zhí)行著戒职,從時間上看這個周期剛好是5秒透乾,為什么是5秒了,因為任務(wù)的執(zhí)行時間為2秒捧韵,在加上設(shè)置的3秒汉操,總共就是5秒了
線程池的關(guān)閉
線程池的有兩個關(guān)閉方法磷瘤,shutdown和shutdownNow
shutdown的作用
關(guān)閉線程池,不再接收新的線程,但線程池中已有的線程任務(wù)會執(zhí)行完畢
shutdownNow
關(guān)閉線程池针炉,不再接受線的線程篡帕,且線程池中已經(jīng)有的線程的任務(wù)也會被立刻終止
demo示例
public class ThreadPool_ScheduledDemo {
public static void main(String[] args) {
ScheduledExecutorService ses = Executors.newScheduledThreadPool(2);
System.out.println("線程池任務(wù)schedule時間:" + System.currentTimeMillis());
// ses.schedule(new MyTask("任務(wù)1"), 2, TimeUnit.SECONDS);
// ses.scheduleAtFixedRate(new MyTask("任務(wù)2"), 2, 3, TimeUnit.SECONDS);
//ses.scheduleWithFixedDelay(new MyTask("任務(wù)3"), 1, 3, TimeUnit.SECONDS);
ses.scheduleWithFixedDelay(new MyTask1("任務(wù)4"), 1, 3, TimeUnit.SECONDS);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ses.shutdown(); //關(guān)閉線程池,不在接收新的線程,但線程池中已有的線程任務(wù)會執(zhí)行完畢
// ses.shutdownNow(); // 關(guān)閉線程池拢军,不再接受線的線程茉唉,且線程池中已經(jīng)有的線程的任務(wù)也會被立刻終止
}
}
class MyTask1 implements Runnable {
private String taskName;
public MyTask1(String taskName) {
this.taskName = taskName;
}
public void run() {
System.out.println(taskName + "開始時間:" + System.currentTimeMillis());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
System.out.println(taskName+"被中斷");
}
System.out.println(taskName + "結(jié)束時間:" + System.currentTimeMillis());
}
}
調(diào)用shutdown方法
運行結(jié)果
線程池任務(wù)schedule時間:1507877541748
任務(wù)4開始時間:1507877542756
任務(wù)4結(jié)束時間:1507877545757
調(diào)用shutdownNow方法
運行結(jié)果
線程池任務(wù)schedule時間:1507877802636
任務(wù)4開始時間:1507877803642
任務(wù)4被中斷
任務(wù)4結(jié)束時間:1507877804641
結(jié)果分析
本來是周期性的任務(wù)赌渣,調(diào)用關(guān)閉方法后,線程池中的任務(wù)就不會在執(zhí)行了
任務(wù)4览芳,在延遲1秒后開始執(zhí)行
任務(wù)內(nèi)部執(zhí)行時間為3秒
主線程會在延遲2秒后關(guān)閉線程池
shutdown方法沧竟,結(jié)束時間-開始時間==3秒,說明的確是在任務(wù)執(zhí)行完畢后才關(guān)閉的
shutdownNow方法杈笔,結(jié)束時間-開始時間==1秒蒙具,說明任務(wù)還沒結(jié)束就已經(jīng)被中斷了
結(jié)論朽肥,推薦使用shutdown