面試官:請你手寫出一個Java線程池?

手把手教你寫出一個Java線程池

線程池架構

  • 一個線程池,應包含阻塞隊列,用來存放任務
  • 包含存放線程的集合,其中的線程用來直接執行任務,或拉取緩存隊列中的任務

注意,下方代碼基本基於本架構圖編寫

自定義拒絕策略

使用函數式接口,將拒絕權限下方,由調用者決定添加任務失敗時線程池的回絕方式

  1. 死等
  2. 帶超時等待
  3. 讓調用者放棄任務執行
  4. 讓調用者拋出異常
  5. 讓調用者自己執行任務
//步驟一:自定義拒絕策略接口
@FunctionalInterface  //函數式接口
interface RejectPolicy<T>{
    void reject(BlockingQueue<T> queue, T task) ;
}

自定義阻塞隊列

阻塞隊列用來暫存任務,超過線程池核心線程數的部分線程,將存放在阻塞隊列中,如果阻塞隊列也滿了,將拒絕繼續向阻塞隊列中添加任務

//自定義阻塞隊列
class BlockingQueue<T>{
    //任務隊列
    private Deque<T> queue = new ArrayDeque<>() ; //雙向隊列:對頭、隊尾都可操作
    //鎖,隊列的頭部元素獲取與尾部元素添加都需要在鎖中完成
    private ReentrantLock lock = new ReentrantLock();
    //生產者條件變量(生產者的等待隊列),在任務隊列滿的時候,添加任務的線程進入等待
    private Condition fullWaitSet = lock.newCondition() ;
    //消費者條件變量,在任務任務隊列空的時候,線程不再集合拉取任務
    private Condition emptyWaitSet = lock.newCondition();
    // 隊列容量
    private int capcity;
    //構造器,只需要調用者給出容量即可
    public BlockingQueue(int capcity) {
        this.capcity = capcity;
    }

    //poll->返回任務隊列頭部的元素給消費者(線程集合)
    // 超時機制版的阻塞獲取,一定時間內無任務則不再進行獲取
    public T poll(long timeout, TimeUnit unit) {

        lock.lock(); //獲取操作需要同步

        try {
        //將 timeout 統一轉換爲 納秒
        long nanos = unit.toNanos(timeout);

        //任務隊列中已經空了(無任務)
        while (queue.isEmpty()) {
            try {
                //等待時間超時,返回空值
                if (nanos <= 0) {
                    return null;
                }
                // 隊列空的等待操作
                nanos = emptyWaitSet.awaitNanos(nanos);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //任務隊列不空,返回隊列頭部元素
        T t = queue.removeFirst();
        //通知向隊列添加任務的線程,可以繼續添加任務
        fullWaitSet.signal();
        return t;
        }
        finally {
            lock.unlock(); //解除鎖
        }
    }

    //普通版阻塞隊列獲取任務
    public T take(){
        lock.unlock();
        try {
            while (queue.isEmpty()){
                try {
                    emptyWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            T t = queue.removeFirst();
            fullWaitSet.signal();
            return t ;
        }finally {
            lock.unlock();
        }
    }

    //阻塞隊列,任務的添加
    public void put(T task){
        lock.lock();  //添加需要上鎖
        try {
            //隊列滿
            while (queue.size()==capcity) {
                try {
                    //進入等待,等待可以放入任務的允許通知
                    System.out.println("等待加入任務隊列"+task);
                    fullWaitSet.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("加入任務隊列"+task);
            queue.addLast(task); //向隊列尾部添加任務
            emptyWaitSet.signal();//告訴消費任務的線程,可以消費了
        }finally {
            lock.unlock(); //解鎖
        }
    }

    //超時機制版阻塞隊列,任務的添加
    public boolean offer(T task,long timeout,TimeUnit unit){
        lock.lock();
        try {
            long nanos = unit.toNanos(timeout);
            //隊列滿
            while (queue.size()==capcity) {
                try {
                    if(nanos <= 0) {
                        return false;
                    }
                    //等待上面的通知就完事
                    System.out.println("等待加入任務隊列"+task);
                     nanos = fullWaitSet.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("加入任務隊列"+task);
            queue.addLast(task);
            emptyWaitSet.signal();//廣播 隊列不爲空了,喚醒等待隊列不爲空的線程(在上面)
            return true;
        }finally {
            lock.unlock();
        }
    }
    //獲取任務隊列大小
    public int size() {
        lock.lock();
        try {
            return queue.size();
        } finally {
            lock.unlock();
        }
    }

    //帶拒絕策略的put,即如果任務隊列無法添加元素,使用何種策略進行拒絕添加
    public void tryPut(RejectPolicy<T> rejectPolicy,T task){
        lock.lock();
        try {
            //判斷隊列是否滿,如滿,執行拒絕策略 --> 策略模式
            if (queue.size() == capcity) rejectPolicy.reject(this,task);
        else {
                System.out.println("加入任務隊列"+task);
                queue.addLast(task);
                emptyWaitSet.signal();//廣播 隊列不爲空了,喚醒等待隊列不爲空的線程(在上面)
            }
        }finally {
            lock.unlock();
        }
    }

}

小結:

  • 本代碼實現了阻塞隊列頭元素獲取及尾元素添加
  • 將阻塞隊列中的各個任務存放於雙端隊列
  • 構造器將初始化該阻塞隊列的核心容量
  • 添加與獲取都提供了超時回空策略,避免長時間無添加與獲取造成的性能浪費

自定義線程池

//自定義線程池
class ThreadPool{
    public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapcity,RejectPolicy<Runnable> rejectPolicy) {
        this.coreSize = coreSize;
        this.timeout = timeout;
        this.timeUnit = timeUnit;
        this.rejectPolicy = rejectPolicy;
        this.taskQueue = new BlockingQueue<>(queueCapcity) ;
    }

    //單個線程的封裝,使用Worker對線程進行了擴展
    class Worker extends Thread{
        private Runnable task ;

        public Worker(Runnable task) {
            this.task = task;
        }
        public void run(){
            // 執行任務
            // 1) 當 task 不爲空,執行任務
            // 2) 當 task 執行完畢,再接着從任務隊列獲取任務並執行
            while (task!=null||(task=taskQueue.poll(timeout,timeUnit))!=null){
                try {
                    System.out.println("正在執行"+task);
                    task.run();
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    task = null ;
                }
            }
            synchronized (workers){
                System.out.println("worker被移除"+this);
                workers.remove(this);
            }
        }
    }
    //任務隊列
    private BlockingQueue<Runnable> taskQueue ;
    //線程集合,集合中使用Worker對線程進行了擴展
    private HashSet<Worker> workers = new HashSet<>();
    //核心線程數
    private int coreSize ;
    //獲取任務時的超時時間
    private long timeout ;
    //超時時間的單位規範
    private TimeUnit timeUnit ;
    //拒絕策略,由主線程自己決定,主線程取實現這個RejectPolicy,並傳入本線程池即可
    private RejectPolicy<Runnable> rejectPolicy ;

    //執行任務
    public void execute(Runnable task){
        // 當任務數沒有超過 coreSize 時,直接交給 worker 對象執行,注意,Work執行完畢後不能立即消失,要被循環利用
        // 如果任務數超過 coreSize 時,加入任務隊列暫存
        synchronized (workers){

            //如果線程集合中的線程數量少於核心線程數,創建Work對象來執行任務
            if (workers.size()<coreSize){
                Worker worker = new Worker(task);
                System.out.println("新增worker"+worker+"task"+task);
                workers.add(worker) ;
                worker.start();
            }else {
                // 各種拒絕策略
                // 1) 死等
                // 2) 帶超時等待
                // 3) 讓調用者放棄任務執行
                // 4) 讓調用者拋出異常
                // 5) 讓調用者自己執行任務

                //加入任務隊列,等待被執行
                taskQueue.tryPut(rejectPolicy,task);
            }
        }
    }
}

小結:

這就是融合了阻塞隊列與拒絕策略的完整線程池了,包括

  • 阻塞隊列
  • 線程集合
  • 執行方法
    • 創建線程來執行任務
      • 如果線程集合中的線程數少於線程池容量,則創建線程直接執行任務
      • 如果線程集合中的線程數大於線程池容量,則將任務放入阻塞隊列,由線程執行完任務後拉去阻塞隊列中的任務來執行

測試方法

public class MyThreadPool {
    public static void main(String[] args) {
        ThreadPool threadPool = new ThreadPool(1,
                1000, TimeUnit.MILLISECONDS, 1, (queue, task)->{
            // 1. 死等
// queue.put(task);
            // 2) 帶超時等待
// queue.offer(task, 1500, TimeUnit.MILLISECONDS);
            // 3) 讓調用者放棄任務執行
// log.debug("放棄{}", task);
            // 4) 讓調用者拋出異常
// throw new RuntimeException("任務執行失敗 " + task);
            // 5) 讓調用者自己執行任務
            task.run();
        });
        for (int i = 0; i < 4; i++) {
            int j = i;
            threadPool.execute(() -> {
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(j); //執行打印j
            });
        }
    }
}

方便演示拒絕策略是否生效,我們指定:

  • 線程池中只有1個線程,超時等待時間爲1s,阻塞隊列容量爲1個

拒絕策略我們選擇:

  • 如果線程池拒絕接收任務,則調用線程池的線程自己把任務執行掉

結果

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