Java裏面線程池的頂級接口是Executor,但是嚴格意義上講Executor並不是一個線程池,而只是一個執行線程的工具,它真正的線程池接口是ExecutorService。
使用線程池能夠爲了防止資源不足,因爲頻繁創建和銷燬線程會消耗大量資源,尤其是當線程執行時間>線程創建時間+線程銷燬時間,此時會堆積大量線程。Java中,創建線程池有四種方式,如下:
1)newCachedThreadPool()
創建一個可緩存線程池,線程池爲無限大,如果線程池長度超過處理需要,可靈活回收空閒線程,即當執行當前任務時上一個任務已經完成,會複用執行上一個任務的線程,而不用每次新建線程;如果上一個線程沒有結束則會新建線程。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CachedThreadPool {
private static ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
public static void main(String args[]) {
for (int i = 0; i < 10; i++) {
try {
if (i == 4) {
Thread.sleep(2000);
}
} catch (Exception e) {
e.getStackTrace();
}
cachedThreadPool.execute(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.getStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在被執行");
}
});
}
}
/**
* 執行結果如下(console輸出):
* pool-1-thread-1正在被執行
* pool-1-thread-4正在被執行
* pool-1-thread-3正在被執行
* pool-1-thread-2正在被執行
* pool-1-thread-4正在被執行
* pool-1-thread-1正在被執行
* pool-1-thread-3正在被執行
* pool-1-thread-2正在被執行
* pool-1-thread-5正在被執行
* pool-1-thread-6正在被執行
*/
}
在i=5的時候,需停留2秒,此時線程1-4都已經釋放,所以1-4線程可以複用,這就是newCachedThreadPool()線程池的優點。
2)newFixedThreadPool()
可重用固定個數的線程池,噹噹前線程數大於總線程數時會進行等待,等待線程池內的線程執行完,相對來說佔用內存少,因爲如果等待線程數量過多,也是相對耗廢資源的。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FixedThreadPool {
private static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);
public static void main(String args[]) {
for (int i = 0; i < 10; i++) {
fixedThreadPool.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + "正在被執行");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.getStackTrace();
}
}
});
}
}
}
線程池大小爲4,所以如果線程執行數量超過4則會進行等待,所以newFixedThreadPool()的缺點也很明顯,就是如果線程池數量設置明顯過小,則會導致大量線程等待,造成資源浪費。
3)newSingleThreadExecutor()
單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO,優先級)執行,如果該線程異常結束,會有另一個線程取代它,保證順序執行。
所以缺點也很明顯,就是單線程執行,線程多,執行速度會比較慢。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SingleThreadPool {
private static ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();
public static void main(String args[]) {
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadPool.execute(new Runnable() {
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "正在執行 index=" + index);
Thread.sleep(1000);
} catch (Exception e) {
e.getStackTrace();
}
}
});
}
}
/**
* 執行結果如下:
* pool-1-thread-1正在執行 index=0
* pool-1-thread-1正在執行 index=1
* pool-1-thread-1正在執行 index=2
* pool-1-thread-1正在執行 index=3
* pool-1-thread-1正在執行 index=4
* pool-1-thread-1正在執行 index=5
* pool-1-thread-1正在執行 index=6
* pool-1-thread-1正在執行 index=7
* pool-1-thread-1正在執行 index=8
* pool-1-thread-1正在執行 index=9
*
* 結論:所有的線程都是線程1來執行而且是有序執行,這就是單線程池!
*/
}
4)newScheduledThreadPool()
可重用固定個數的線程池,當前線程數大於總數則會進行等待,並且可以設置線程延遲執行時間。
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPool {
private static ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4);
public static void main(String args[]) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmsssss");
for (int i = 0; i < 10; i++) {
System.out.println(i + "開始執行時間" + sdf.format(new Date()));
scheduledThreadPool.schedule(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + "正在執行 執行時間=" + sdf.format(new Date()));
}
}, 3, TimeUnit.SECONDS);
}
}
}
那麼,使用線程池有沒有什麼風險呢?
1)死鎖:任何的多線程都有可能發生死鎖的風險(線程之間互相等待)
2)資源不足:線程池太大造成,正常來說合理創建線程池大小一般不會出現這個問題
3)線程泄漏和請求過載