多线程-线程池的基本使用及原理

基本认识

jdk中提供了Executors框架获取几类线程池,经典的四类single(单例),fixed(固定),cache(缓存),Schedul(定时)。工作方式(如果不满足就流向下一步),创建核心线程---->队列存储--->创建救急线程--->拒绝策越。

其中单例和固定线程池队列采用LinkBlockQueue无界队列当池中没有可用的线程时,优先放入队列中,cache使用同步有界队列,该队列特点是无容量,即有任务就创建线程不会往队列放,Schedul采用延迟队列,该队列会对存放的任务加上执行优先级属性。

       //ExecutorService executor = Executors.newFixedThreadPool(2); //2个线程 无界队列
       //ExecutorService executor = Executors.newSingleThreadExecutor(); //1个线程 无界队列
       //ExecutorService executor = Executors.newCachedThreadPool(); //5个线程 有界同步队列
       //ExecutorService executor = Executors.newScheduledThreadPool(3);

常用方法

submit(Callable task)----->执行Callable任务,返回Future对象。

execute (Runnable tasj)------>执行Runbale任务,无返回值。

invokeAll(Collection<Callable> tasks)---->执行批量Callable任务,返回List<Future>对象,代表每个任务的执行结果。

invokeAny(Collection<Callable> tasks) ---->执行批量Callable任务中的一个,返回object对象,代表执行任务的执行结果。

shutdown()--->停止线程池,如果任务已经在池中则会执行完才停止。

shutdownNow()--->停止线程池,如果任务已经在池中则会强制停止。

 底层原理及自定义线程池实现

线程池组成部分,核心线程大小,最大线程大小,救急线程过期时间,救急线程时间单位,阻塞队列,拒绝策越,线程工厂名。根据这些其实本地可以实现一个简单线程池,主要是针对阻塞队列的设计,线程类的设计。

阻塞队列

        设计思路:成员变量有队列大小,基本队列,当队列满时,外部任务应等待队列中的任务取出执行完才放入队列,否则阻塞。同理队列任务为空时,超过一定时间,线程池就关闭。执行任务的过程中需要加锁放置多个线程执行同一任务,还需要两个waitSet放阻塞的任务。所以还需要一个锁,两个WaitSet。方法:入队,出队,获取队列长度。代码如下

/**
 * 阻塞队列
 * 设计 有界,有序,push及pull应保证线程安全
 */
@Slf4j
class MyBolckQueue<T> {
    private Deque<T> deque = new ArrayDeque<>();

    private ReentrantLock lock = new ReentrantLock();

    private Condition proSet = lock.newCondition();

    private Condition consumeSet = lock.newCondition();

    private int capCity;

    public MyBolckQueue(int capCity) {
        this.capCity = capCity;
    }

    /**
     * 拉取消息,如果消息为空,则消费者进入等待,如果消费了消息,则唤醒生产者队列可继续放任务
     *
     * @param
     */
    public T poll(long timeout, TimeUnit unit) {
        lock.lock();
        try {
            // 将 timeout 统一转换为 纳秒
            long nanos = unit.toNanos(timeout);
            while (deque.isEmpty()) {
                try {
                    // 返回值是剩余时间
                    if (nanos <= 0) {
                        return null;
                    }
                    nanos = consumeSet.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            T t = deque.removeFirst();
            proSet.signal();
            return t;
        } finally {
            lock.unlock();
        }
    }

    /**
     * 存放任务,如果队列满了,则生产者等待。存入任务后需通知消费者消息
     *
     * @return
     */
    public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
        lock.lock();
        try {
            // 判断队列是否满
            if (deque.size() == capCity) {
                rejectPolicy.reject(this, task);
            } else {  // 有空闲
                log.debug("加入任务队列 {}", task);
                deque.addLast(task);
                consumeSet.signal();
            }
            log.info("此时队列大小{}", deque.size());
        } finally {
            lock.unlock();
        }
    }

 线程类

        设计思路:成员变量有Runable任务,方法有run方法重写即可。构造方法接收任务对象。

class Worker extends Thread {
        private Runnable task;

        public Worker(Runnable task) {
            this.task = task;
        }

        @Override
        public void run() {
            //此处的run需要不断去执行新任务或者从队列中取任务
            while (task != null || (task = taskQueue.poll(1, timeUnit)) != null) {
                try {
                    log.debug("正在执行...{}", task);
                    task.run();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    task = null;
                }
            }
            synchronized (workers) {
                log.debug("worker 被移除{}", this);
                workers.remove(this);
            }

        }
    }

线程池类

    设计思路:包含线程集合,核心线程大小,超时时间,时间单位,拒绝策越,阻塞队列。其中线程基本对象,阻塞队列已经设计好,拒绝策越是函数式接口,其它都是基本属性。方法有execute()方法执行任务,判断逻辑跟JDK线程池逻辑一致。代码如下

@Slf4j
class ThreadPool {
    private MyBolckQueue<Runnable> taskQueue;

    private HashSet<Worker> workers = new HashSet<>();

    private int coreSize;

    private TimeUnit timeUnit;

    private long expireTime;

    private RejectPolicy rejectPolicy;

    public ThreadPool(MyBolckQueue<Runnable> taskQueue, int coreSize, TimeUnit timeUnit, long expireTime, RejectPolicy rejectPolicy) {
        this.taskQueue = taskQueue;
        this.coreSize = coreSize;
        this.timeUnit = timeUnit;
        this.expireTime = expireTime;
        this.rejectPolicy = rejectPolicy;
    }

    /**
     * execute提交一个Runable任务
     *
     * @param task
     */
    public void excute(Runnable task) {
        //如果工作线程小于核心线程数,则可以创建一个线程对象,否则进入队列等待
        synchronized (workers) {
            //此处加锁,避免执行同一个任务
            if (workers.size() < coreSize) {
                Worker worker = new Worker(task);
                log.info("创建核心线程{}运行任务{}", worker, task);
                workers.add(worker);
                worker.start();
            } else {
                //尝试加入阻塞队列,如果不成功执行拒绝策越
                taskQueue.tryPut(rejectPolicy, task);
            }
        }
    }
}

拒绝策越及测试方法 

@Slf4j
public class ExcutorPools {

    public static void main(String[] args) {
        MyBolckQueue objectMyBolckQueue = new MyBolckQueue<>(1);
        ThreadPool pool = new ThreadPool(
                objectMyBolckQueue,
                2,
                TimeUnit.SECONDS,
                1,
                (objectMyBolckQueue1, runnable1) -> {
                    log.info("结束等待{}", runnable1);
                }
        );
        for (int i = 0; i < 4; i++) {
            int j = i;
            pool.excute(() -> {
                log.debug("{}", j);
            });
        }
    }
}

@FunctionalInterface // 拒绝策略
interface RejectPolicy<T> {
    void reject(MyBolckQueue<T> queue, T task);
}

测试结果:

任务0,1创建核心线程来执行。任务2发现核心线程已有2个,进入阻塞队列。任务3发现队列也满,执行拒绝策越。任务0,1执行完成后,取队列任务执行。最终一段时间没任务,线程销毁。

猜你喜欢

转载自blog.csdn.net/weixin_42740540/article/details/124863789