java線程池原理--如何實現線程和任務分離,並保證線程在一定時間內不銷燬的?

先說下結論:

如何實現線程和任務分離呢?

線程和任務都是實現了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()

線程池基本原理和實現線程不銷燬的基本方式,我們就清楚了.在這個基礎上,對於其他的一些問題,我們也可以進行分析了.

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章