Java多线程与高并发八(线程池)

       Java多线程与高并发七本来想写ThreadLocal源码的,实在是看底层代码看不动了!先搁置吧。Java多线程与高并发八给同学们分享下面试中常考的线程池的七大参数!

为什么要用线程池

       防止频繁创建和销毁线程,让每个线程可以多次使用,防止消耗过多内存,所以我们使用线程池。

为什么不用JDK自带线程池

        像下面这样定义线程池,不香吗?为什么不用呢?

    ExecutorService executorService = Executors.newFixedThreadPool(5);

        阿里的《阿里巴巴 Java 开发手册》已经说得很清楚明白了,我这里就直接盗用吧。

Java多线程与高并发八(线程池)_第1张图片

自己创建线程池的目的是让我们自己更加明确线程池的运行规则!

如何创建线程池

  • 线程池的七大参数
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        //实现略
    }
corePoolSize:线程池持有的可用的线程数量
maximumPoolSize:当队列workQueue满了过后,线程池可用的最大线程数量
keepAliveTime:大于核心线程数的帮工线程们等待新任务到来的等待时间,超过该时间,帮工线程们还没有可执行的任务,这些线程就会关闭unit:上个参数keepAliveTime的单位,通常是以,TimeUnit.SECONDS,这样给出的,表示秒
workQueue:任务堆积的等待队列,比如当前核心线程数是5,来了10个,那么后来的5个就会放到该队列中
threadFactory:线程工厂,主要是给线程赋予特定名称
handler:拒绝策略,当队列满了,帮工线程们也起来了,还在往线程池里加线程该怎么处理?这个策略就是定义怎么处理的。
  • 线程池的创建案例
import java.util.concurrent.*;

/**
 * 线程池测试
 *
 * @author zab
 * @date 2020-01-21 22:40
 */
public class ThreadPoolTest {
    private static final int CORE_POOL_SIZE = 5;
    private static final int MAX_POOL_SIZE = 8;
    private static final int KEEP_ALIVE_TIME = 60;

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

        ThreadPoolTest t = new ThreadPoolTest();

        ThreadPoolExecutor e = t.getThreadPool2();

        for (int i = 0; i < 100; i++) {
            e.execute(t.new MyRunnable(i));
        }
        
    }

    private ThreadPoolExecutor getThreadPool0() {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10),
                new MyThreadFactory("whatBusiness"),
                new ThreadPoolExecutor.AbortPolicy()
        );

        return threadPoolExecutor;
    }

    private ThreadPoolExecutor getThreadPool1() {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10),
                new MyThreadFactory("whatBusiness"),
                new ThreadPoolExecutor.DiscardPolicy()
        );

        return threadPoolExecutor;
    }

    private ThreadPoolExecutor getThreadPool2() {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10),
                new MyThreadFactory("whatBusiness"),
                new ThreadPoolExecutor.DiscardOldestPolicy()
        );

        return threadPoolExecutor;
    }

    private ThreadPoolExecutor getThreadPool3() {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10),
                new MyThreadFactory("whatBusiness"),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );

        return threadPoolExecutor;
    }

    private ThreadPoolExecutor getThreadPool4() {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(10),
                new MyThreadFactory("whatBusiness"),
                new MyRejectedExecutionHandler()
        );

        return threadPoolExecutor;
    }

    class MyRejectedExecutionHandler implements RejectedExecutionHandler {

        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            System.out.println("做自己想要的逻辑");
            System.out.println("核心池大小:" + executor.getCorePoolSize());
        }
    }


    class MyThreadFactory implements ThreadFactory {

        private String threadName;

        public MyThreadFactory(String threadName) {
            this.threadName = threadName;
        }

        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            t.setName(threadName+t.getName());
            return t;
        }
    }

    class MyRunnable implements Runnable {

        int count = 0;

        public MyRunnable(int count){
            this.count = count;
        }

        @Override
        public void run() {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread---"+Thread.currentThread().getName()+"----count:"+count);
        }
    }
}

这里定义getThreadPool的几个方法主要是获取不同拒绝策略的线程池:

getThreadPool0:获取拒绝策略的线程池
getThreadPool1:获取丢弃策略的线程池
getThreadPool2:获取丢弃最老策略的线程池
getThreadPool3:获取调用者执行策略的线程池
getThreadPool4:获取自定义策略的线程池

然后自定义了几个内部类:

MyRejectedExecutionHandler自定义拒绝策略
MyThreadFactory自定义线程工厂
MyRunnable自定义线程类

main方法很简单,就是获取不同的线程池,for循环跑100次,然后撑爆线程池的等待队列,测试不同拒绝策略的表现。

线程池拒绝策略分析

1、抛异常策略

Java多线程与高并发八(线程池)_第2张图片

可以看到核心线程数5,最大线程数8,循环执行100次,当核心线程数满了后,再来新的线程,就会往等待队列里放,这里的等待队列是一个ArrayBlockingQueue,容量是10,再来线程满了后,会创建【帮工线程】,可以看到线程名最大是7,说明该测试已经把【帮工线程】都用上了,再来线程就会执行拒绝策略,可以看到控制台有抛异常。

2、丢弃策略

丢弃策略什么都不干,其他分析同拒绝策略,值得注意的是,控制台5、6、7线程对应的计数为什么是15、16、17。设想一个场景:

银行有八个窗口,其中五个是打开状态,有人络绎不绝的来办理业务,一个、两个、三个...当第五个人来的时候,如果前面的人都未办理完业务,那么第五个人一定走向最后一个打开的窗口,这时候又来了第六个、第七个...这个时候,保安见状,略有所思:今儿个业务太好了,得让客户排队了!最后第十五个人来的时候,前面都还没人办理完业务,排队都排了10个人了。。。第十六个人来的时候,保安终于坐不住了,去通知大堂经理,大堂经理见状,立即安排了三个业务员把剩余的窗口打开,保安就安排第十六、十七、十八个客户去了新开的窗口办理业务了,而排队的10个人依旧排着队。

以上场景看似不合理,但是确实是代码执行中看到的现象的形象描述。

3、丢弃最老等待线程策略

 这个可以看到,等待最久的线程都被忽略了,新来的得到执行。控制台对应的count是for循环的下标,最后执行的下标都达到99了,这说明循环中比较小的数,例如第77次、第88次这些都是等待比较久的线程,都已经被丢弃了。

结合上述案例来说,就好比先来的等待办理业务的客户等不及了,离开银行了。

4、谁调用谁执行策略

Java多线程与高并发八(线程池)_第3张图片

这个比较简单,就是main方法调用的线程池执行的,那么线程池的线程跑不完的就给调用者main方法执行。

 5、自定义策略

Java多线程与高并发八(线程池)_第4张图片

这个可以看到,一旦有新的线程既排不了队,又不能执行,就会执行自定义的策略。这里没有策略,只是输出信息。

 线程池新线程添加流程

  1. 当前池内线程数小于核心线程数时,有任务来,新建一个线程
  2. 当前池内线程数大于核心线程数时,有任务来,队列没排满时,就排队
  3. 当前池内线程数大于核心线程数时,有任务来,队列排满时,线程池继续new线程处理任务,直到线程池线程数等于最大线程池数为止
  4. 当前池内线程数等于最大线程数时,有任务来,队列未满时,排队处理;队列满时,执行拒绝策略

你可能感兴趣的:(Java并发编程)