Java實現終止執行緒池中正在執行的定時任務

haifeiWu發表於2019-03-04

源於開發

最近專案中遇到了一個新的需求,就是實現一個可以動態新增定時任務的功能。說到這裡,有人可能會說簡單啊,使用quartz就好了,簡單粗暴。然而quartz框架太重了,小專案根本不好操作啊。當然,也有人會說,jdk提供了timer的介面啊,完全夠用啊。但是我們專案的需求完全是多執行緒的模型啊,而timer是單執行緒的,so,樓主最後還是選擇了jdk的執行緒池。

執行緒池是什麼

Java通過Executors提供四種執行緒池,分別為:
**newCachedThreadPool :**建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒。
newFixedThreadPool : 建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。
newScheduledThreadPool : 建立一個定長執行緒池,支援定時及週期性任務執行。
newSingleThreadExecutor : 建立一個單執行緒化的執行緒池,它只會用唯一的工作執行緒來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先順序)執行。

樓主專案中用到的是newScheduledThreadPool, 就這些吧,再多的樓主就班門弄斧了,Google一下,一大堆。

執行緒池service的獲取

樓主通過單例模式來獲取執行緒池的service,程式碼如下:

/**
 * 執行緒池建立.
 * @author wuhf
 * @date 2018/01/16
 */
public class ThreadPoolUtils {

    private static ScheduledExecutorService executorService;

    private ThreadPoolUtils() {
        //手動建立執行緒池.
        executorService = new ScheduledThreadPoolExecutor(10,
                new BasicThreadFactory.Builder().namingPattern("syncdata-schedule-pool-%d").daemon(true).build());
    }

    private static class PluginConfigHolder {
        private final static ThreadPoolUtils INSTANCE = new ThreadPoolUtils();
    }

    public static ThreadPoolUtils getInstance() {
        return PluginConfigHolder.INSTANCE;
    }

    public ScheduledExecutorService getThreadPool(){
        return executorService;
    }

}
複製程式碼

中斷某一個正在執行的執行緒程式碼實現

廢話就不多說了,程式碼如下:

/**
 * 中斷執行緒池的某個任務.
 */
public class InterruptThread implements Runnable {

    private int num;

    public InterruptThread (int num){
        this.num = num;
    }

    public static void main(String[] args) throws InterruptedException {

        Thread interruptThread = new Thread(new InterruptThread(1));
        ScheduledFuture<?> t = ThreadPoolUtils.getInstance().getThreadPool().scheduleAtFixedRate(interruptThread,0,2,
                TimeUnit.SECONDS);

        InterruptThread interruptThread1 = new InterruptThread(2);
        ThreadPoolUtils.getInstance().getThreadPool().scheduleAtFixedRate(interruptThread1,0,2,
                TimeUnit.SECONDS);

        InterruptThread interruptThread2 = new InterruptThread(3);
        ThreadPoolUtils.getInstance().getThreadPool().scheduleAtFixedRate(interruptThread2,0,2,
                TimeUnit.SECONDS);
        Thread.sleep(5000);

		//終止正在執行的執行緒interruptThread
        t.cancel(true);
        while (true){

        }
    }

    @Override
    public void run() {
        System.out.println("this is a thread" + num);
    }
}
複製程式碼

踩坑記錄

樓主在使用如下程式碼時,突然想到當這個定時任務需要被停止時該如何停止執行緒執行

ThreadPoolUtils.getInstance().getThreadPool().scheduleAtFixedRate(interruptThread,0,2,
                TimeUnit.SECONDS);
複製程式碼

既然我有這樣的需求,那就Google一下吧,找了大半圈,愣是沒找到相關資料,都是一些關於Java執行緒池的深入分析。或者是全域性變數啥的,並沒有找到令樓主滿意的解決方案。

既然沒有執行緒的那就扒一下scheduleAtFixedRate的底層原始碼看看是什麼東西吧,果不其然我在原始碼中看到了scheduleAtFixedRate方法的具體實現,發現他的返回值是ScheduledFuture。

 public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
        if (period <= 0)
            throw new IllegalArgumentException();
        ScheduledFutureTask<Void> sft =
            new ScheduledFutureTask<Void>(command,
                                          null,
                                          triggerTime(initialDelay, unit),
                                          unit.toNanos(period));
        RunnableScheduledFuture<Void> t = decorateTask(command, sft);
        sft.outerTask = t;
        delayedExecute(t);
        return t;
    }
複製程式碼

接著往下我們再看看ScheduledFuture裡面有什麼東西吧,沒有讓樓主失望,看到了這個

public boolean cancel(boolean mayInterruptIfRunning) {
            boolean cancelled = super.cancel(mayInterruptIfRunning);
            if (cancelled && removeOnCancel && heapIndex >= 0)
                remove(this);
            return cancelled;
}
            
//從執行緒的執行佇列中移除當前執行緒
public boolean remove(Runnable task) {
        boolean removed = workQueue.remove(task);
        tryTerminate(); // In case SHUTDOWN and now empty
        return removed;
}
複製程式碼

再往上查super.cancel(mayInterruptIfRunning)是什麼東西,我們看到了這個,

//通過呼叫執行緒的interrupt方法終止執行緒執行
public boolean cancel(boolean mayInterruptIfRunning) {
        if (!(state == NEW &&
              UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
                  mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
            return false;
        try {    // in case call to interrupt throws exception
            if (mayInterruptIfRunning) {
                try {
                    Thread t = runner;
                    if (t != null)
                        t.interrupt();
                } finally { // final state
                    UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
                }
            }
        } finally {
            finishCompletion();
        }
        return true;
    }
複製程式碼

到這裡所有的問題都迎刃而解。

總結一下吧

專案中總是會遇到比較難搞的解決方案,當Google不太好找時,翻一下jdk的原始碼或許也是一個不錯的方法。最後宣傳一下樓主的部落格,部落格已經遷移到騰訊雲上,跟幾個大學的小夥伴一起經營,內容涵蓋java,Android等,感興趣的小夥伴請移步樓主跟他的小夥伴們的部落格

相關文章