先說下結論:
如何實現線程和任務分離呢?
線程和任務都是實現了Runnable接口的類,不同的是,扮演線程的類通過start()方法創建線程並執行其中的run()方法,而扮演任務的類則不會調用start()方法,在線程的run()方法中能獲取到任務的引用,並直接調用任務的run方法(無論是直接在run()方法中使用,還是在somemethod()方法中,然後在run()方法中調用somemethod()).
如何實現不銷燬的呢?
首先,糾正一點,有人說在使用線程池的時候,從中找一個空閒的線程拿過來用,用完了再放回線程池,這個說法很形象,但是並不準確.實際情況是,我們並不需要知道哪些空閒,也不是我們拿過來用,也不需要在用完以後放回線程池,我們只需要不停地提交給線程池我們需要完成的任務,線程池就會自己去創建線程(一定條件下)執行任務,或者那些已經創建的空閒的線程,在發現新的任務時,會主動去爭取這個任務,並執行.而線程之所以不會被銷燬,是因爲要麼,它在執行任務,要麼它在等待任務.java線程池中的線程,會在創建之初被分配一個任務,在這個任務執行完後,就會去BlockingQueue阻塞隊列中拿任務,如果拿不到,就會阻塞在那裏,拿到了就執行,執行完了,繼續去拿,如此循環往復(指定線程的存活時間,則等待任務一定時間後,還是拿不到,就會被銷燬了).
如何使用線程池?
public static void testThreadPoolExecutor() {
//1.創建線程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5));
for(int i = 0; i < 15; i ++){
MyTask myTask = new MyTask(i);
//2.提交任務到線程池
executor.execute(myTask);
System.out.println("線程池中線程數目:"+executor.getPoolSize()+",隊列中等待執行的任務數目:"+
executor.getQueue().size()+",已執行完別的任務數目:"+executor.getCompletedTaskCount());
}
//3.關閉線程池
executor.shutdown();
}
其中,自定義的task:
//1.實現Runnable接口,或者實現Thread類
class MyTask implements Runnable {
private int taskNum;
public MyTask(int num) {
this.taskNum = num;
}
//2.重寫run方法
@Override
public void run() {
System.out.println("正在執行task "+taskNum + " 當前線程id爲:" + Thread.currentThread().getId());
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("task "+taskNum+"執行完畢");
}
}
在執行提交任務到線程池的代碼時,發生了什麼呢?且看源碼:
public void execute(Runnable command) {
if (command == null)
//1.拋異常
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
//2.調用addWorker()方法
if (addWorker(command, true))
return;
c = ctl.get();
}
//3.添加到workQueue
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
//4.拒絕任務
reject(command);
else if (workerCountOf(recheck) == 0)
//5.調用addWorker()方法
addWorker(null, false);
}
else if (!addWorker(command, false))
//6.調用addWorker()方法失敗,則拒絕任務
reject(command);
}
提交任務到線程池,可能會有下面幾種情況:
1.拋出異常
2.調用addWorker()方法
3.將任務添加到workQueue隊列
4.拒絕任務
拋出異常和拒絕任務沒什麼好說的了,提交任務到隊列,就是把任務添加到隊列裏,也沒什麼.現在需要研究的是addWorker()方法,做了什麼?
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (int c = ctl.get();;) {
// Check if queue empty only if necessary.
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP)
|| firstTask != null
|| workQueue.isEmpty()))
return false;
for (;;) {
if (workerCountOf(c)
>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateAtLeast(c, SHUTDOWN))
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
//上面的代碼主要是檢查一些參數,看還能不能添加worker,主要看下面的代碼
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//1.這裏又worker,又task,又thread的,他們之間什麼關係呢?
//work是根據task創建的,thread t是從worker來的
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int c = ctl.get();
if (isRunning(c) ||
(runStateLessThan(c, STOP) && firstTask == null)) {
if (t.getState() != Thread.State.NEW)
throw new IllegalThreadStateException();
//2.這裏把新的worker添加到線程池的workers集合中了
workers.add(w);
workerAdded = true;
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
//3.這裏一個線程開始運行了,就是新創建的worker中的thread
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
上面源碼中的註釋3處,線程開始運行,實際執行的run()方法在哪裏呢?
先看下ThreadPoolExecutor.Worker類的源碼:
//1.注意Worker實現了Runnable接口
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
//省略
//2.主要看這三個屬性
/** Thread this worker is running in. Null if factory fails. */
@SuppressWarnings("serial") // Unlikely to be serializable
final Thread thread;
/** Initial task to run. Possibly null. */
@SuppressWarnings("serial") // Not statically typed as Serializable
Runnable firstTask;
/** Per-thread task counter */
volatile long completedTasks;
//3.主要看這兩個方法,構造方法中,worker和自己的屬性糾纏不清
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker. */
public void run() {
runWorker(this);
}
//省略...
}
上面提到,addWorke()方法中,最後有線程啓動了,但是真正被執行的run方法在哪裏呢?是不是Worker類中的run方法呢?
可以通過調試,加斷點的方法證明,確實實際執行的run()方法就是Worker類中的run方法!同時也說明,此時啓動的線程是新的worker的線程,在它的run()方法中雖然調用了線程池的runWorker()方法,但是,這個方法是在這個worker的線程中運行的.
接下來看下線程池的runWorker()方法的源碼:
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//1.循環獲取task,從worker中獲取task,或者通過getTask()方法獲取task
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
try {
//2.首先應該明確這個runWorker()方法是在這個worker的線程中執行的,所以task的run方法雖然是作爲普通方法調用的,但是也是在worker的線程中執行的.
task.run();
afterExecute(task, null);
} catch (Throwable ex) {
afterExecute(task, ex);
throw ex;
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
一個worker對應線程池中的一個線程.在上面的源碼中,我們可以看到,worker線程會循環獲取任務,要麼獲取最初提交到worker中的task,要麼通過調用線程池中的getTask()方法獲取任務.這裏也實現了線程和任務的分離(線程和任務都是實現了Runnable接口的類).我們可以簡單分析一下,最初提交到worker的任務執行完後,worker就會通過線程池的getTask()方法獲取任務,如果能獲取到,就可以繼續處理了,如果獲取不到呢?爲什麼沒有被銷燬呢?這就要看下getTask()的源碼了:
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
//1.這是一個無限循環
for (;;) {
int c = ctl.get();
// Check if queue empty only if necessary.
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
//2.上面的是特殊情況的處理,看下面代碼,從workQueue中獲取任務
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
上面的workQueue是線程池的屬性:
private final BlockingQueue<Runnable> workQueue;
阻塞隊列,阻塞隊列有什麼特點呢?或者說它的poll()和take()方法有什麼特點呢?
/**
* Retrieves and removes the head of this queue, waiting if necessary
* until an element becomes available.
*
* @return the head of this queue
* @throws InterruptedException if interrupted while waiting
*/
E take() throws InterruptedException;
/**
* Retrieves and removes the head of this queue, waiting up to the
* specified wait time if necessary for an element to become available.
*
* @param timeout how long to wait before giving up, in units of
* {@code unit}
* @param unit a {@code TimeUnit} determining how to interpret the
* {@code timeout} parameter
* @return the head of this queue, or {@code null} if the
* specified waiting time elapses before an element is available
* @throws InterruptedException if interrupted while waiting
*/
E poll(long timeout, TimeUnit unit)
throws InterruptedException;
註釋說的是什麼呢?調用這兩個方法的線程,在獲取不到元素時,會阻塞.poll和take的區別就是前者可以指定等待時間,後者則不行.
在覈心線程獲取不到任務時,如果沒有指定核心線程的存活時間,會一直阻塞在take方法上,如果指定了核心線程的存活時間,則線程會阻塞在poll方法上,直到等待時間耗盡.這就實現了核心線程不銷燬.
調用的順序如下:
ThreadPoolExecutor.execute()---->ThreadPoolExecutor.addWorker()---->Worker.run()---->ThreadPoolExecutor..runWorker()---->BlockingQueue.take() || pBlockingQueue.take()||BlockingQueue.poll()
線程池基本原理和實現線程不銷燬的基本方式,我們就清楚了.在這個基礎上,對於其他的一些問題,我們也可以進行分析了.