线程池的原理和使用(二)

线程池的任务调度流程:

1:如果当前工作线程数量小于核心线程数量,执行器总是优先创建一个任务线程,而不是从线程队列中获取一个空闲线程.

2:如果线程池中总的任务数量大于核心线程数量.新接收的任务将被加入阻塞队列中.一直到阻塞队列满,在核心线程数量已经用完,并且阻塞队列没有满,线程池不会为新任务创建一个新线程.

3:当完成一个任务执行的时候.执行器总是优先从阻塞队列中获取下一个任务,并开始执行,一直到阻塞队列为空.

4:在核心线程数量用完,并且阻塞队列也已经满的情况下,如果线程池接收的新的任务,就会创建一个新的线程(非核心线程)执行这个任务.

5:在核心线程都用完,阻塞队列也已经满了,非核心线程加上核心线程大于最大线程数,对新来的任务就会执行拒绝策略.

不合理线程池配置
public class CreateThreadPoolErrorDemo {

    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                //核心线程数.
                1,
                //最大线程数.
                100,
                //空闲存活时长.
                100,
                //存活时长单位.
                TimeUnit.SECONDS,
                //阻塞队列.
                new LinkedBlockingDeque<>(100));
        //提交五个任务.
        for (int i = 0; i < 5; i++) {
            final int taskIndex = i;
            executor.execute(() -> {
                System.out.println("taskIndex+" + taskIndex);
                //极端测试.
                try {
                    Thread.sleep(Integer.MAX_VALUE);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        while (true) {
            System.out.println("工作任务数量-->" + executor.getActiveCount() 
                    + "总任务数-->" + executor.getTaskCount());
            Thread.sleep(1000);
        }
    }
}

从以上例子可以知道:

1:核心线程和最大线程数量阻塞队列等参数如果配置不合理,可能会造成异步任务无法达到预期的并发执行,造成严重的排队等待现象.

2:线程调度器创建线程的一条重要规则是:在核心线程已满以后,只有阻塞队列满了以后,才会去创建新的线程.

ThreadFactory(线程工厂) 

ThreadFactory是java线程工厂的一个接口.

public interface ThreadFactory {

    /**
     * Constructs a new {@code Thread}.  Implementations may also initialize
     * priority, name, daemon status, {@code ThreadGroup}, etc.
     *
     * @param r a runnable to be executed by new thread instance
     * @return constructed thread, or {@code null} if the request to
     *         create a thread is rejected
     */
    Thread newThread(Runnable r);
}

用线程工厂创建新线程时,可以更改新线程的名称 线程组 优先级 守护进程状态等,如果返回值为null,表示线程未能创建成功,线程池可能无法执行任务.使用Executors创建线程时可以指定ThreadFactory实例,如果没有指定的话,就会使用默认的线程工厂.

public static ThreadFactory defaultThreadFactory() {
        return new DefaultThreadFactory();
    }

下面贴出一个关于线程工厂的例子.

public class ThreadFactoryDemo {

    //一个简单的线程工厂.
    static public class SimpleThreadFactory implements ThreadFactory {

        static AtomicInteger threadNo = new AtomicInteger(1);

        //实现唯一的线程创建方法.
        @Override
        public Thread newThread(Runnable r) {
            String threadName = "simpleThread-" + threadNo.get();
            System.out.println("创建一条线程为,名称为" + threadName);
            threadNo.getAndIncrement();
            //设置线程名称和异步执行目标.
            Thread thread = new Thread(r, threadName);
            //设置为守护线程.
            thread.setDaemon(true);
            return thread;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService threadPool = 
     Executors.newFixedThreadPool(2, new SimpleThreadFactory());

        for (int i = 0; i < 5; i++) {
            threadPool.submit(()->{
                System.out.println("我是手动创建的线程在执行.");
            });
        }
        Thread.sleep(1000);
        System.out.println("关闭线程池.");
        threadPool.shutdown();
    }
}

任务阻塞队列 

java阻塞队列和普通队列有一个区别是,阻塞队列为空时,会阻塞当前线程的获取操作.有数据了会唤醒对应的线程.

阻塞队列BlockingQueue的实例
1:ArrayBlockingQueue

是一个数组实现的有界阻塞队列,队列中的元素按FIFO排序.在创建时必须设置大小.接收任务超过核心线程数量的时候会放入该阻塞队列,若阻塞队列已满的话,会创建新的线程,直到线程数量等于最大线程数.

2:LinkedBlockingQueue

是一个基于链表实现的阻塞队列,队列中的元素按FIFO排序.可以设置队列的大小,为有界队列,不设置的话默认用Integer.MAX_VALUE,是无界队列.该队列的吞吐量高于ArrayBlockingQueue.

如果不设置该队列的容量的话,核心线程数量达到,会因为无限接收任务而导致资源耗尽.

3:PriorityQueue

具有优先级的阻塞队列.

4:DelayQueue

是一个无界延迟阻塞队列,底层基于PriorityQueue实现,队列中每个元素都有过期时间,从队列获取元素时,只有已经过期的元素才会出队,而队列头部是最先过期的元素.

5:SynchronousQueue(同步队列)

这是一个不存储元素的阻塞队列,,每个插队操作必须等到另一个线程的调用移除操作.否则插入一条数据一直处于,吞吐量通常高于LinkedBlockingQueue,与前面的队列相比,这个队列不会存储任务,而是直接创建一个线程执行.

调度器的钩子方法

ThreadPool线程调度器为每个任务执行前后都提供了钩子方法.

//任务执行前的钩子方法. 
protected void beforeExecute(Thread t, Runnable r) { }

//任务执行后的钩子方法.
protected void afterExecute(Runnable r, Throwable t) { }

//线程终止的钩子方法.
protected void terminated() { }
beforeExecute:异步任务执行之前的钩子方法.

线程池工作线程在异步执行目标实例前调用此钩子方法.此方法仍由执行任务的工作线程调用.此钩子方法的默认实现不执行任何操作,可以在调度器子类中对其进行自定义.

此方法由执行目标实例的工作线程调用,可用于初始化ThreadLocal线程本地变量实例 更新日志记录 开始计时统计 更新上下文变量.

afterExecute:异步任务执行之后的钩子方法.

线程池工作线程在异步执行目标实例后调用此钩子方法.此方法仍由执行任务的工作线程调用.此钩子方法的默认实现不执行任何操作,可以在调度器子类中对其进行自定义.

terminated:线程终止时的钩子方法.

在Executor终止时调用,默认不实现任何操作.

public class ThreadPoolDemo {

    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,
                4,
                60,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(2)) {
            @Override
            protected void terminated() {
                System.out.println("调度器停止工作");
            }

            @Override
            protected void beforeExecute(Thread t, Runnable r) {
                System.out.println(r + "执行前工作.");
                super.beforeExecute(t, r);
            }

            @Override
            protected void afterExecute(Runnable r, Throwable t) {
                super.afterExecute(r, t);
                System.out.println(r + "执行后工作.");
            }
        };

        for (int i = 0; i < 5; i++) {
            threadPool.execute(()->{
                System.out.println("执行任务.");
            });
        }
        Thread.sleep(1000);
        threadPool.shutdown();
    }
}

线程池的拒绝策略:

任务拒绝有两种情况:

1:阻塞队列已经满了,并且已经达到了最大线程数.

2:线程池关闭.

不管哪种情况调用拒绝策略,线程池都会调用这个接口的实例.

public interface RejectedExecutionHandler {

    /**
     * Method that may be invoked by a {@link ThreadPoolExecutor} when
     * {@link ThreadPoolExecutor#execute execute} cannot accept a
     * task.  This may occur when no more threads or queue slots are
     * available because their bounds would be exceeded, or upon
     * shutdown of the Executor.
     *
     * <p>In the absence of other alternatives, the method may throw
     * an unchecked {@link RejectedExecutionException}, which will be
     * propagated to the caller of {@code execute}.
     *
     * @param r the runnable task requested to be executed
     * @param executor the executor attempting to execute this task
     * @throws RejectedExecutionException if there is no remedy
     */
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
拒绝策略:
public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * Creates an {@code AbortPolicy}.
         */
        public AbortPolicy() { }

        /**
         * Always throws RejectedExecutionException.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         * @throws RejectedExecutionException always
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }

使用该策略,如果线程池队列满了,新任务就会被拒绝,并且抛出RejectedExecutionHandler异常,这个是线程池的默认策略.

抛弃策略:
public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardPolicy}.
         */
        public DiscardPolicy() { }

        /**
         * Does nothing, which has the effect of discarding task r.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

如果线程池的队列满了,新任务会直接丢掉,不会抛出任何异常. 

抛弃最老任务策略:
 public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardOldestPolicy} for the given executor.
         */
        public DiscardOldestPolicy() { }

        /**
         * Obtains and ignores the next task that the executor
         * would otherwise execute, if one is immediately available,
         * and then retries execution of task r, unless the executor
         * is shut down, in which case task r is instead discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }

 使用该策略,如果队列满了,就会将最早进入的队列的任务抛弃,从队列中腾出空间,在尝试加入队列,因为队列是队尾进,队头出.,所以每次都是移除队头的元素在尝试入队. 

调用者执行策略:
public static class CallerRunsPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code CallerRunsPolicy}.
         */
        public CallerRunsPolicy() { }

        /**
         * Executes task r in the caller's thread, unless the executor
         * has been shut down, in which case the task is discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

在新任务被添加到线程池时,如果添加失败,那么提交任务的线程会自己去执行该任务.

自定义拒绝策略: 
class CustomIgnorePolicy implements RejectedExecutionHandler {
        @Override
        public void rejectedExecution(Runnable r, 
          ThreadPoolExecutor executor) {
            //做日志记录等.
            System.out.println("任务" + r + "-getTaskCount:" 
            + executor.getTaskCount());
            //可以自己搞一个线程自己处理.
            Thread thread = new Thread(r);
            thread.start();
        }
    }

这块完全可以发挥自己的想象力,比如发送mq 发送邮件其实都是可以的.

优雅的关闭线程池:

了解关闭线程池之前,先看下线程池的五种状态.

private static final int RUNNING    = -1 << COUNT_BITS;
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    private static final int STOP       =  1 << COUNT_BITS;
    private static final int TIDYING    =  2 << COUNT_BITS;
    private static final int TERMINATED =  3 << COUNT_BITS;
RUNNING:

线程池创建后的初始状态,这种状态下可以执行任务.

SHUTDOWN:

该状态下线程不会在接收任务,但是会将工作队列中剩余的任务执行完成.

STOP:

该状态下的线程池不会在接受任务,也不会在执行队列中剩下的任务,也会中断所有线程.

TIDYING:

该状态下所有任务都已终止或者处理完成,将会执行terminated()钩子方法.

TERMINATED:

执行完terminated()钩子方法之后的状态.

线程池的状态转换规则:

1:线程池创建后为RUNNING.

2:执行线程池的shutdown实例方法,会使线程池状态从RUNNING转变为SHUTDOWN.

3:执行线程池的shutdownNow实例方法,会使线程池的状态从RUNNING变为STOP.

4:当前线程池处于SHUTDOWN状态,执行其shutdownNow方法会让其状态变为STOP.

5:等待线程池的所有工作线程停止,工作队列清空之后,线程池状态会从STOP转变为TIDYING,

6:执行完terminated钩子方法以后,线程池状态从TIDYING转变为TERMINATED.

优雅的关闭线程池涉及的方法有三种:
1:shutdown方法

是juc提供的一个有序关闭线程池的方法,此方法会等待当前队列中的剩余任务全部执行完成才会执行关闭,此方法调用以后线程池的状态会变为SHUTDOWN,线程池不会在接收新的任务.

2:shutdownNow方法

是juc提供的一个立即关闭线程池的方法,此方法会打断正在执行的工作线程,并且会清空当前工作队列中剩余的任务,返回的是尚未执行的任务.

3:awaitTermination方法

等待线程池完成关闭.在调用线程池的shutdown和shutdownNow方法当前线程会立即返回,不会一直等待线程池关闭.如果需要等待线程池关闭可以使用awaitTermination方法.

追逐的路上,也许我会慢一些,但是我一定不会放弃.

如果大家喜欢我的分享的话,可以关注一下微信公众号.

心有九月星辰

 
 
 
 
 
 
 
 
 
 

                
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值