工作中,經常會涉及到線程。比如有些任務,經常會交與線程去異步執行。抑或服務端程序為每個請求單獨建立一個線程處理任務。線程之外的,比如我們用的數據庫連接。這些創建銷毀或者打開關閉的操作,非常影響系統性能。所以,“池”的用處就凸顯出來了。
在3.6.1節介紹的實現方式中,對每個客戶都分配一個新的工作線程。當工作線程與客戶通信結束,這個線程就被銷毀。這種實現方式有以下不足之處:
相對來說,使用線程池,會預創建一些線程,它們不斷的從工作隊列中取出任務,然後執行該任務。當工作線程執行完一個任務後,就會繼續執行工作隊列中的另一個任務。優點如下:
下面是自己寫的一個簡單的線程池,也是從Java網絡編程這本書上直接照著敲出來的
package thread;
import java.util.LinkedList;
/**
* 線程池的實現,根據常規線程池的長度,最大長度,隊列長度,我們可以增加數目限制實現
* @author Han
*/
public class MyThreadPool extends ThreadGroup{
//cpu 數量 ---Runtime.getRuntime().availableProcessors();
//是否關閉
private boolean isClosed = false;
//隊列
private LinkedList<Runnable> workQueue;
//線程池id
private static int threadPoolID;
private int threadID;
public MyThreadPool(int poolSize){
super("MyThreadPool."+threadPoolID);
threadPoolID++;
setDaemon(true);
workQueue = new LinkedList<Runnable>();
for(int i = 0;i<poolSize;i++){
new WorkThread().start();
}
}
//這裡可以換成ConcurrentLinkedQueue,就可以避免使用synchronized的效率問題
public synchronized void execute(Runnable task){
if(isClosed){
throw new IllegalStateException("連接池已經關閉...");
}else{
workQueue.add(task);
notify();
}
}
protected synchronized Runnable getTask() throws InterruptedException {
while(workQueue.size() == 0){
if(isClosed){
return null;
}
wait();
}
return workQueue.removeFirst();
}
public synchronized void close(){
if(!isClosed){
isClosed = true;
workQueue.clear();
interrupt();
}
}
public void join(){
synchronized (this) {
isClosed = true;
notifyAll();
}
Thread[] threads = new Thread[activeCount()];
int count = enumerate(threads);
for(int i = 0;i<count;i++){
try {
threads[i].join();
} catch (Exception e) {
}
}
}
class WorkThread extends Thread{
public WorkThread(){
super(MyThreadPool.this,"workThread"+(threadID++));
System.out.println("create...");
}
@Override
public void run() {
while(!isInterrupted()){
System.out.println("run..");
Runnable task = null;
try {
//這是一個阻塞方法
task = getTask();
} catch (Exception e) {
}
if(task != null){
task.run();
}else{
break;
}
}
}
}
}
該線程池主要定義了一個工作隊列和一些預創建的線程。只要調用execute方法,就可以向線程提交任務。
後面線程在沒有任務的時候,會阻塞在getTask(),直到有新任務進來被喚醒。
join和close都可以用來關閉線程池。不同的是,join會把隊列中的任務執行完,而close則立刻清空隊列,並且中斷所有的工作線程。close()中的interrupt()相當於調用了ThreadGroup中包含子線程的各自的interrupt(),所以有線程處於wait或者sleep時,都會拋出InterruptException
測試類如下:
public class TestMyThreadPool {
public static void main(String[] args) throws InterruptedException {
MyThreadPool pool = new MyThreadPool(3);
for(int i = 0;i<10;i++){
pool.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("working...");
}
});
}
pool.join();
//pool.close();
}
}
java提供了很好的線程池實現,比我們自己的實現要更加健壯以及高效,同時功能也更加強大。
類圖如下:

關於這類線程池,前輩們已經有很好的講解。任意百度下java線程池,都有寫的非常詳細的例子和教程,這裡就不再贅述。
java自帶線程池和隊列詳解
在使用spring框架的時候,如果我們用java提供的方法來創建線程池,在多線程應用中非常不方便管理,而且不符合我們使用spring的思想。(雖然spring可以通過靜態方法注入)
其實,Spring本身也提供了很好的線程池的實現。這個類叫做ThreadPoolTaskExecutor。
在spring中的配置如下:
<bean id="executorService" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="${threadpool.corePoolSize}" />
<!-- 線程池維護線程的最少數量 -->
<property name="keepAliveSeconds" value="${threadpool.keepAliveSeconds}" />
<!-- 線程池維護線程所允許的空閒時間 -->
<property name="maxPoolSize" value="${threadpool.maxPoolSize}" />
<!-- 線程池維護線程的最大數量 -->
<property name="queueCapacity" value="${threadpool.queueCapacity}" />
<!-- 線程池所使用的緩沖隊列 -->
</bean>
任何多線程程序都有死鎖的風險,最簡單的情形是兩個線程AB,A持有鎖1,請求鎖2,B持有鎖2,請求鎖1。(這種情況在mysql的排他鎖也會出現,不會數據庫會直接報錯提示)。線程池中還有另一種死鎖:假設線程池中的所有工作線程都在執行各自任務時被阻塞,它們在等待某個任務A的執行結果。而任務A卻處於隊列中,由於沒有空閒線程,一直無法得以執行。這樣線程池的所有資源將一直阻塞下去,死鎖也就產生了。
如果線程池中的線程數目非常多,這些線程會消耗包括內存和其他系統資源在內的大量資源,從而嚴重影響系統性能。
線程池的工作隊列依靠wait()和notify()方法來使工作線程及時取得任務,但這兩個方法難以使用。如果代碼錯誤,可能會丟失通知,導致工作線程一直保持空閒的狀態,無視工作隊列中需要處理的任務。因為最好使用一些比較成熟的線程池。
使用線程池的一個嚴重風險是線程洩漏。對於工作線程數目固定的線程池,如果工作線程在執行任務時拋出RuntimeException或Error,並且這些異常或錯誤沒有被捕獲,那麼這個工作線程就異常終止,使線程池永久丟失了一個線程。(這一點太有意思)
另一種情況是,工作線程在執行一個任務時被阻塞,如果等待用戶的輸入數據,但是用戶一直不輸入數據,導致這個線程一直被阻塞。這樣的工作線程名存實亡,它實際上不執行任何任務了。如果線程池中的所有線程都處於這樣的狀態,那麼線程池就無法加入新的任務了。
當工作線程隊列中有大量排隊等待執行的任務時,這些任務本身可能會消耗太多的系統資源和引起資源缺乏。
綜上所述,使用線程池時,要遵循以下原則:
分類: Java網絡編程 標簽: 線程池