模仿实现线程池,提高使用并发容器的熟练度,加深对并发的认识

package github.com.AllenDuke.concurrentTest.threadPoolTest;

import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @description 简单模仿线程池,原理基本一致(原生的设计还是很复杂,很巧妙的,
* 例如用一个AtomicInteger去保存多个状态,使得多个状态的切换成原子性)
* 先由核心线程处理,处理不来则加入队列,队列若满则新建非核心线程,达最大线程数则拒绝。 * 每个线程完成当前任务后(还没有进入空闲,避免在空闲与非空闲之间频繁切换,而且造成线程不安全,因为如果此时有新任务进来,主线程尝试 * 为它设定任务,而它自己又尝试从队列中拉取,由此覆盖,造成任务丢失)尝试从任务队列中拉取任务, * 拉取失败(队列已空)进入空闲状态(自己不再尝试从队列中拉取,而是‘轮询等待’主线程分配,注意防止分配到一半就去拉取), * 非核心线程记录和空闲时间有关的数据,拉取成功重置。 * 非核心线程空闲一定时间后消亡,消亡后要从空闲非核心线程中移除,否则会造成任务丢失。 * shutDonw后拒绝任务,所有存活线程在消费完任务队列后消亡。 * @contact AllenDuke@163.com *
@since 2019/11/30 */ public class ThreadPoolService { private int coreSize = 2;//核心线程数 private int maxSize = 4;//最大线程数 private long keepAlive = 2 * 1000;//空闲时间,单位毫秒 private volatile boolean isShutDown = false; private ConcurrentLinkedQueue<Runnable> taskQueue = new ConcurrentLinkedQueue();//并发任务队列,控制并发拉取 private int queueCapacity = 10;//任务队列容量 private AtomicInteger queueSize = new AtomicInteger(0);//当前任务队列大小,原子变量控制并发提交 private RejectHandler rejectHandler = new MyRejectHandler();//拒绝策略 private ConcurrentLinkedQueue<CoreThread> freeCorePool = new ConcurrentLinkedQueue();//空闲核心线程队列 private ConcurrentLinkedQueue<NonCoreThread> freeNonCorePool = new ConcurrentLinkedQueue();//空闲非核心线程队列 private AtomicInteger curSize = new AtomicInteger(2);//当前线程数,原子变量控制并发新建和消亡 //初始化空闲核心线程队列 { CoreThread core0 = new CoreThread(); core0.setName("core0"); CoreThread core1 = new CoreThread(); core1.setName("core1"); freeCorePool.add(core0); freeCorePool.add(core1); } /** * @param task 任务 * @description: 有以下几种情况: * 1.如果线程池已经关闭则直接拒绝 * 2.成功交付空闲线程 * 3.成功加任务队列 * 4.如果队列已满且已达最大线程数,那么拒绝 * @return: void * @date: 2020/2/10 */ public void execute(Runnable task) { if (isShutDown) reject((task)); else if(!executeByCore(task)&&!executeByNonCore(task)&&!waitInQueue(task)) { if(curSize.get()==maxSize) reject(task);//有可能刚询问完,队列就为空了 else if(curSize.incrementAndGet()<=maxSize){//注意并发新建非核心线程 NonCoreThread nonCore=new NonCoreThread(); nonCore.setName("nonCore"+(curSize.get()-2)); freeNonCorePool.add(nonCore); executeByNonCore(task); }else{//新建失败 curSize.decrementAndGet(); reject(task); } } } /** * @param task * @description: 尝试从空闲的核心线程队列中弹出一个线程,任务就给该核心线程执行 * @return: boolean true为交付成功,false为交付失败 * @date: 2020/2/10 */ private boolean executeByCore(Runnable task) { CoreThread core = freeCorePool.poll(); if (core != null) { core.setTask(task); if (core.getState() == Thread.State.NEW) core.start(); return true; } else return false; } /** * @param task * @description: 尝试把任务加入队列,用原子变量控制 * @return: boolean true为添加成功,false为添加失败 * @date: 2020/2/10 */ private boolean waitInQueue(Runnable task) { //如果+1后仍在容量内,则添加成功 if (queueSize.incrementAndGet() <= queueCapacity) { taskQueue.add(task); System.out.println("加入队列任务——" + task); return true; } //+1后超出容量,则添加失败,需要把-1 else { queueSize.decrementAndGet(); return false; } } /** * @param task * @description: 尝试从空闲的非核心线程队列中弹出一个线程,任务就给该非核心线程执行 * @return: boolean true为交付成功,false为交付失败 * @date: 2020/2/10 */ private boolean executeByNonCore(Runnable task) { NonCoreThread nonCore = freeNonCorePool.poll(); if (nonCore != null) { nonCore.setTask(task); if (nonCore.getState() == Thread.State.NEW) nonCore.start(); return true; } else return false; } //拒绝当前任务 private void reject(Runnable task) { rejectHandler.reject(task); } //关闭线程池,拒绝任务,每个线程消费完任务队列后消亡 public void shutDown() { isShutDown = true; System.out.println("线程池关闭"); } /** * @description: 核心线程不断工作,完成当前任务后会尝试从队列中拉取任务,在shutDown前不会消亡 * @date: 2020/2/10 */ class CoreThread extends Thread { private volatile boolean isFree = false;//volatile禁止指令重排序,且确保主线程的修改对它本身可见 private volatile Runnable task; /** * @description: 尝试从队列拉取任务,若成功则设置当前任务,失败则当前线程回到空闲核心线程队列 * @return: void * @date: 2020/2/10 */ public void pullTask() { Runnable task = taskQueue.poll(); if (task != null) { setTask(task); queueSize.decrementAndGet(); } else {//失败说明任务队列已为空 isFree = true; freeCorePool.add(this); } } public void setTask(Runnable task) { isFree = false; this.task = task; } @Override public void run() { while (!isShutDown || !taskQueue.isEmpty()) { if (task != null) { task.run(); System.out.println(Thread.currentThread().getName() + "完成任务——" + task); task = null; } //这个判断条件是很苛刻的 if(!isFree&&task==null) pullTask();//二者都用volatile遵循happens-bofore,防止主线程修改到一半就去拉取 } System.out.println(Thread.currentThread().getName() + "消亡"); } } /** * @description: 非核心线程不断工作,完成当前任务后会尝试从队列中拉取任务,在空闲一定时间后消亡 * @date: 2020/2/10 */ class NonCoreThread extends Thread { private volatile boolean isFree = false;//volatile确保主线程的修改对它本身可见 private long beginFree; private volatile Runnable task; /** * @param task * @description: 每一次设置任务后重置空闲标志 * @return: void * @date: 2020/2/10 */ public void setTask(Runnable task) { this.task = task; isFree = false; } /** * @description: 尝试从队列拉取任务,若成功则重新设置任务, * 若失败,不是空闲则设置空闲标记,进行空闲计时,当前线程回到空闲队列 * @return: void * @date: 2020/2/10 */ public void pullTask() { Runnable task = taskQueue.poll(); if (task != null) { setTask(task); queueSize.decrementAndGet(); } else {//失败说明任务队列已为空 isFree = true; beginFree = System.currentTimeMillis(); freeNonCorePool.add(this); } } /** * @param * @description: 在没有shutDown且(在忙或者剩余时间 > 0)时不会消亡, * 每次完成任务后将当前任务置空 * 总是尝试从队列中拉取任务 * 消亡时,当前线程数-1 * @return: void * @date: 2020/2/10 */ @Override public void run() { while ((!isShutDown || !taskQueue.isEmpty()) && (!isFree || System.currentTimeMillis() - beginFree < keepAlive)) { if (task != null) { task.run(); System.out.println(Thread.currentThread().getName() + "完成任务——" + task); task = null; } if(!isFree&&task==null) pullTask(); } curSize.decrementAndGet(); freeNonCorePool.remove(this);//把消亡的线程从队列中移除,消亡后的线程不为null,不会被上面的executrByNonCore感知到,进而造成任务丢失 System.out.println(Thread.currentThread().getName() + "消亡"); } } }
package github.com.AllenDuke.concurrentTest.threadPoolTest;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @description 任务类
 * @contact AllenDuke@163.com
 * @since 2019/11/30
 */
public class Task implements Runnable{

    private static AtomicInteger sum=new AtomicInteger(0);

    public int num;

    public Task(){num=sum.getAndIncrement();}

    @Override
    public void run() {
        try {
            System.out.println(Thread.currentThread().getName() + "准备执行任务——" + num);
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public String toString() {
        return num+"";
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadPoolService threadPoolService=new ThreadPoolService();
        //并发提交
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                threadPoolService.execute(new Task());
            }
        }).start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                threadPoolService.execute(new Task());
            }
        }).start();
        Thread.sleep(3000);
        for (int i = 0; i < 20; i++) {
            threadPoolService.execute(new Task());
        }
        //threadPoolService.shutDown();
    }
}
package github.com.AllenDuke.concurrentTest.threadPoolTest;

/**
 * @description 拒绝策略
 * @contact AllenDuke@163.com
 * @since 2020/2/10
 */
public interface RejectHandler {

    void reject(Runnable task);
}
package github.com.AllenDuke.concurrentTest.threadPoolTest;

/**
 * @description
 * @contact AllenDuke@163.com
 * @since 2020/2/10
 */
public class MyRejectHandler implements RejectHandler {
    @Override
    public void reject(Runnable task) {
        System.out.println("队列已满,已达最大线程数,无空闲线程,抛弃当前任务——"+task.toString());
    }
}

 值得注意的是:

/**
* ThreadPoolExecutor用的是BlockingQueue,这里用的ConcurrentLinkedQueue
* 二者设计差别在于:
* 前者:
* 如果核心线程执行完当前任务后,尝试阻塞地从阻塞队列中拉取任务。
* 如果非核心线程执行完当前任务后,尝试超时地从阻塞队列中拉取任务,若返回null则消亡。
* 因此新任务进来是优先加入队列的,意味着这时候如果有大量任务并发过来,线程池只会接受队列大小(或者加上新建非核心线程数)
* 节省cpu资源,但仍有线程状态切换的消耗,新任务并发数小(当然只是差了一个线程数而已),这方面设计较简单
* 后者:
* 如果核心线程执行完当前任务后,尝试从队列中拉取任务,若拉取失败,进入空闲自旋(消耗cpu),等待分配任务
* 如果非核心线程执行完当前任务后,尝试从队列中拉取任务,若拉取失败,进入空闲自旋,等待分配任务,进行空闲计时,到时消亡
* 新任务进来优先分配给空闲线程,而后进入队列,意味着可接受(线程数+队列大小)的任务数
* 没有线程状态切换的消耗,并发数较前者大
* 但这方面的设计较为复杂,要严格控制线程空闲与在忙状态切换的条件,以免主线程分配了任务而自己有取队列拉取,造成丢失
* 而要主线程的分配是线程安全的话,就必须要求分配时涉及的行为是线程安全的
* 所以下面多出用到了线程安全的容器、变量,volatile关键字。
* 总结:
* 前者适合任务偶发提交(有时间隔挺久的)的情况
* 后者适合任务频繁提交(即自旋时间少)的情况
*/

下面简单分析jdk线程池的一段代码
public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))//新建核心线程处理
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {//加入队列
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);//新建非核心线程处理
        }
        else if (!addWorker(command, false))
            reject(command);
    }

//runWork
final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            //完成当前任务后阻塞获取,其中非核心线程超时返回null则退出
            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);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);//线程退出
        }
    }

//Worker.getTask
private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= 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;
            }

            try {
                //阻塞(超时)获取任务
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

 

 
posted @ 2020-02-10 19:41  Allen没有青春  阅读(222)  评论(0)    收藏  举报