不使用線程池時的一些問題
public class NonePool {
public static void main(String[] args) {
new Thread(){
public void run() {
//TODO
};
}.start();
}
}
上面的代碼新建了一個線程,在線程結束的時候,線程會自動被回收
在生產環境中可能需要創建若干個線程來完成系統的功能,當需要的線程數量很多的時候就會需要分配大量的線程,此時還是使用這種方法就會有以下問題:
1. 創建大量的線程需要消耗大量的內存個CPU
2. 線程本身也需要佔據大量的內存和CPU
3. 線程執行完後需要回收,回收的時候需要大量的GC操作
綜上,在系統中,線程的數量並不是越多就越會提升系統的性能,需要有一個限度
線程池
爲了避免頻繁的創建線程和銷燬線程,我們可以讓創建的線程進行復用,類似於數據庫連接池。
JDK中支持的線程池
- public static ExecutorService newFixedThreadPool(int nThreads)
含有固定數量爲nThreads的線程的線程池,提交任務的時候,如果有線程則立即執行,沒有的話則先將任務暫存在一個任務隊列中 - public static ExecutorService newSingleThreadExecutor()
只有一個線程的線程池,若有多餘的任務被提交,那麼先將任務保存在一個隊列中,待線程空閒,按照FIFO的策略執行下一個線程 - public static ExecutorService newCachedThreadPool()
返回一個可以根據實際情況調整線程數量的線程池。線程池的數量是不確定的,有空閒線程可以複用的話,那麼就優先使用這些線程。如果沒有空閒的線程,那麼會創建新的線程處理任務,線程執行完畢後,將返回線程池進行服用,默認的情況下如果一個線程超過60秒沒有被使用,那麼就會被回收 - public static ScheduledExecutorService newSingleThreadScheduledExecutor()
線程池中線程的數量爲1,不過返回的是ScheduledExecutorService對象,意味着可以在指定的時間執行任務
5.public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
類似於第四種,不過可以指定線程中線程的數量
線程池案例分析
newFixedThreadPool
public class FixedThreadPool {
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
for(int i=0;i<10;i++){
fixedThreadPool.submit(new MyTask(i));
}
}
}
class MyTask implements Runnable{
private int i;
public MyTask(int i) {
super();
this.i = i;
}
@Override
public void run() {
System.out.println(new Date().getTime()+"___"+Thread.currentThread().getId()+"___"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
1497167121692___13___4
1497167121692___10___1
1497167121692___11___2
1497167121692___12___3
1497167121692___9___0
1497167122692___13___5
1497167122692___9___6
1497167122693___10___7
1497167122694___11___8
1497167122694___12___9
分析:
可以看出第一批執行的時間是1497167121692,第二批的五個是1497167122692,並且前5個的執行的線程的id與後5個是一樣的,證明的確創建了5個線程,並且可以被複用
newScheduledThreadPool
scheduleAtFixedRate
public class ScheduledThreadPool {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(10);
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId()+"\tbegin\t"+System.currentTimeMillis());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getId()+"\tend\t"+System.currentTimeMillis());
}
}, 0, 2, TimeUnit.SECONDS);
}
}
public ScheduledFuture
9 begin 1497178292150
9 end 1497178293151
9 begin 1497178294151
9 end 1497178295152
11 begin 1497178296151
11 end 1497178297151
9 begin 1497178298151
9 end 1497178299151
注意
如果每個任務需要耗費的時間大於period,怎麼辦?
將上面的任務的休眠1s改爲休眠8s,結果如下:
9 begin 1497178511226
9 end 1497178519227
9 begin 1497178519227
9 end 1497178527228
11 begin 1497178527229
11 end 1497178535229
9 begin 1497178535229
9 end 1497178543229
可以看出此時執行後續第一個任務(第一次執行不算)的時間是initialDelay+8s,執行後續第二個任務的時間是initialDelay+2*8s,也就是說在同一時刻只會有一個任務在實行,並且執行的週期爲任務需要花費的時間與週期的大小之間去大值
scheduleWithFixedDelay
同樣的上面的代碼,如果將scheduledAtFixedRate改成scheduleWithFixedDelay,就會變成後續的任務會在前面的任務執行完畢後period時間執行,上面程序的運行結果就是
9 begin 1497178896324
9 end 1497178897324
9 begin 1497178899324
9 end 1497178900325
11 begin 1497178902326
11 end 1497178903326
9 begin 1497178905327
9 end 1497178906327
可以看出每一個任務的開始都是在上一個任務結束就的2秒鐘才執行的