在上一篇文章中《java線程池學習(一) —— BlockingQueue》,我們簡單探討了一下BlockingQueue的概念。
那麼在這邊文章,我們要利用BlockingQueue來自己實現一個簡單的線程池,在以後的章節中,我們再學習一下怎麼去使用java爲我們封裝好的線程池。
首先我們關注一個 “生產者消費者” 的情景。
生產者:不斷產生新的需要解決的任務,比如查詢數據庫,執行某些業務邏輯等。
消費者:不斷解決產生的問題。
那麼把這兩者連接起來的就要用到我們的BlockingQueue了。
生產者將不斷產生的任務放入到隊列中,如果隊列滿了,生產者等待。
消費者不斷的從隊列中取出任務解決,當隊列空了,消費者等待新任務到來。
首先BlockQueue的長度我們要限制,不然如果解決者的解決能力跟不上生產者的,這個任務隊列就會越來越多。
接着我們還需要限定問題解決者的個數,就是我們所謂的線程池中能同時運行的最多的線程數,如果線程數太多的話會嚴重影響系統的穩定性。
那麼我們根據這兩個參數寫一個簡單的線程池:
線程池:
public class ThreadPool {
//用blockingQueue創建一個任務隊列,初始化長度爲5
private BlockingQueue<Runnable> tasksQueue = new ArrayBlockingQueue<Runnable>(5);
//定義線程池中消費者最大數量
private int consumers = 3;
//這個方法提供給所有的任務生產者,產生新的任務插入
public void insertTask(Runnable task) throws InterruptedException{
tasksQueue.put(task);
}
//線程池的初始化
ThreadPool(){
//激活消費者,等待問題到來
for(int i=1;i<=consumers;i++){
Solver consumer = new Solver(tasksQueue,i);
consumer.start();
}
}
}
接下來定義 消費者 邏輯:
public class Solver extends Thread{
//引用線程池的任務隊列,消費者不斷的從裏面取得任務去解決
private BlockingQueue<Runnable> taskQueue = null;
String name;
Solver(BlockingQueue<Runnable> tasks,int name){
this.taskQueue = tasks;
this.name = String.valueOf(name);
}
public void run(){
try {
while(true){
//從隊列中取出任務執行,注意這裏用了take方法,所以如果隊列空了,那麼線程會等待,直到有任務來了,繼續執行
Runnable task = taskQueue.take();
System.out.println("消費者"+name+"接收了一個任務");
task.run();
System.out.println("消費者"+name+"解決了一個任務");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
我們在上面的例子可以看到。
這個線程池中最大的線程數是3,就是最多隻能同時有3個消費者線程執行,消費者會監視線程池的任務隊列,只要隊列中有任務,就會取出來執行。
接下來我們定義 生產者 的邏輯:
public class ProblemCreater {
public static void main(String[] args) throws Exception {
//初始化線程池
ThreadPool threadPool = new ThreadPool();
//生成者不斷產生任務
for(int i=1;i<10;i++){
//定義一個新的任務
Runnable task = new Runnable(){
public void run(){
Random random = new Random();
//隨機一個數字模擬需要解決的時間
int randomTime = Math.abs(random.nextInt())%20;
//System.out.println("這個任務需要解決時間爲:"+randomTime);
try {
Thread.sleep(randomTime*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//將問題插入到線程池任務隊列中
threadPool.insertTask(task);
System.out.println("插入新的任務"+i);
}
}
}
到此我們已經實現好了一個非常簡單的線程池,將線程的創建與執行過程分離開,而不是將線程的生命週期管理和任務的執行過程綁定在一起,如果只是想簡單的丟一個任務進去執行,我們只需要將任務的執行過程封裝到一個Runnable接口中就可以了。而對於那些需要返回結果的任務,我們可以將其封裝到Callable接口裏面。
當然java線程池比我們自己寫的這個高大上很多,在以後的篇幅中,我們再做具體研究。