手记

干货,谈谈对java线程池的理解(面试必备)

线程池的概念

线程池由任务队列和工作线程组成,它可以重用线程来避免线程创建的开销,在任务过多时通过排队避免创建过多线程来减少系统资源消耗和竞争,确保任务有序完成;ThreadPoolExecutor 继承自 AbstractExecutorService 实现了 ExecutorService 接口,ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor 实现了 ExecutorService 和 ScheduledExecutorService 接口

//有多个构造方法,最终都指向这个最多参数的构造方法
  public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

corePoolSize:核心运行的线程个数,也就是当超过这个范围的时候就需要将新的异步任务放入到等待队列中,小于这个数时添加进来的异步任务一般直接新建Thread 执行;

maximumPoolSize:最大线程个数,当大于了这个值就会将准备新加的异步任务由一个丢弃处理机制来处理,大于 corePoolSize 且小于 maximumPoolSize 则新建 Thread 执行,但是当通过newFixedThreadPool 创建的时候,corePoolSize 和 maximumPoolSize 是一样的,而corePoolSize 是先执行的,所以他会先被放入等待队列而不会执行到下面的丢弃处理中;

workQueue:任务等待队列,当达到 corePoolSize的时候就向该等待队列放入线程信息(默认为一个LinkedBlockingQueue);

keepAliveTime:默认是 0,当线程没有任务处理后空闲线程保持多长时间,不推荐使用;

threadFactory:是构造 Thread 的方法,一个接口类,可以使用默认的 default实现,也可以自己去包装和传递,主要实现 newThread 方法即可;

defaultHandler:当参数 maximumPoolSize 达到后丢弃处理的方法实现,java 提供了 5种丢弃处理的方法,当然也可以自己弄,主要是要实现接口 RejectedExecutionHandler 中rejectedExecution(Runnabler, ThreadPoolExecutor e) 方法,java 默认使用的是AbortPolicy,他的作用是当出现这种情况的时候抛出一个异常;通常得到线程池后会调用其中的 submit 或 execute 方法去提交执行异步任务,其实 submit 方法最终会调用execute 方法来进行操作,只是他提供了一个 Future
来托管返回值的处理而已,当你调用需要有返回值的信息时用它来处理是比较好的,这个 Future 会包装 Callable 信息。

BlockingQueue有四个具体的实现类,根据不同需求,选择不同的实现类
1、ArrayBlockingQueue:一个由数组支持的有界阻塞队列,规定大小的BlockingQueue,其构造函数必须带一个int参数来指明其大小.其所含的对象是以FIFO(先入先出)顺序排序的。
2、LinkedBlockingQueue:大小不定的BlockingQueue,若其构造函数带一个规定大小的参数,生成的BlockingQueue有大小限制,若不带大小参数,所生成的BlockingQueue的大小由Integer.MAX_VALUE来决定.其所含的对象是以FIFO(先入先出)顺序排序的。
3、PriorityBlockingQueue:类似于LinkedBlockQueue,但其所含对象的排序不是FIFO,而是依据对象的自然排序顺序或者是构造函数的Comparator决定的顺序。
4、SynchronousQueue:特殊的BlockingQueue,对其的操作必须是放和取交替完成的。
LinkedBlockingQueue 可以指定容量,也可以不指定,不指定的话,默认最大是Integer.MAX_VALUE,其中主要用到put和take方法,put方法在队列满的时候会阻塞直到有队列成员被消费,take方法在队列空的时候会阻塞,直到有队列成员被放进来。

说了这么多的概念估计啥也不清楚,带大家写个例子,一边看代码一边看概念会理解的很快

public class ThreadPoolTest {
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = Math.max(4, Math.min(CPU_COUNT - 1, 5));
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 2;
    private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<>(10);

 //一共执行20个任务 ,核心线程数是4,最大核心线程数是10,目前加入的runnable20个(相当于20个任务),
 //20个任务需要执行,但是核心线程数只有4个,还有16个任务,由于LinkedBlockingQueue队列是最大存放的任务为10   个,队列满了,则会创建新的线程去执行任务,这个时候最大线程是10, 非核心线LinkedBlockingQueue数还有6个,这时候会开6个线程去执行,  目前达到10个最大线程数,此时队列里面还有10个。正好满足队列的大小

    static {
        System.out.println("核心线程数=" + CORE_POOL_SIZE);
        System.out.println("最大线程数=" + MAXIMUM_POOL_SIZE);

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,  //核心线程数
                MAXIMUM_POOL_SIZE, //线程池中最大的线程数
                60,  //线程的存活时间,没事干的时候,空闲的时间
                TimeUnit.SECONDS, //线程存活时间的单位
                sPoolWorkQueue, //线程缓存队列
                new ThreadFactory() {  //线程创建工厂,如果线程池需要创建线程会调用newThread来创建
                    @Override
                    public Thread newThread(@NonNull Runnable r) {
                        Thread thread = new Thread(r);
                        thread.setDaemon(false);
                        return thread;
                    }
                });
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("执行完毕" + Thread.currentThread().getName());
                }

            };
            //丢给线程池去执行
            THREAD_POOL_EXECUTOR.execute(runnable);
        }
    }
}

核心的解释,大家请看注释

运行效果

在看个例子

public class ThreadPoolTest {
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE = Math.max(4, Math.min(CPU_COUNT - 1, 5));
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 2;
    private static final BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<>(9);

    //一共执行20个任务 ,核心线程数是4,最大核心线程数是10,目前加入的runnable20个(相当于20个任务),
    //20个任务需要执行,但是核心线程数只有4个,还有16个任务,由于LinkedBlockingQueue队列是最大存放的任务为9个,队列满了,则会创建新的线程去执行任务
    //这个时候最大线程是10,非核心线程数还有6个,这时候会开6个线程去执行,目前达到10个最大线程数,此时队列里面最大只能存放9个,
    //还有一个Runnable,此时就会报错RejectedExecutionException

    static {
        System.out.println("核心线程数=" + CORE_POOL_SIZE);
        System.out.println("最大线程数=" + MAXIMUM_POOL_SIZE);

        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,  //核心线程数
                MAXIMUM_POOL_SIZE, //线程池中最大的线程数
                60,  //线程的存活时间,没事干的时候,空闲的时间
                TimeUnit.SECONDS, //线程存活时间的单位
                sPoolWorkQueue, //线程缓存队列
                new ThreadFactory() {  //线程创建工厂,如果线程池需要创建线程会调用newThread来创建
                    @Override
                    public Thread newThread(@NonNull Runnable r) {
                        Thread thread = new Thread(r);
                        thread.setDaemon(false);
                        return thread;
                    }
                });
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 20; i++) {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("执行完毕" + Thread.currentThread().getName());
                }

            };
            //丢给线程池去执行
            THREAD_POOL_EXECUTOR.execute(runnable);
        }
    }
}

这个例子就是把LinkedBlockingQueue的大小改为了9个,具体的解释,请大家看注释;大家可以清楚的知道RejectedExecutionException 报错的原因,其实是AsyncTask一些隐患,比如去执行200个Runnable 肯定会报错

运行效果

相信大家看例子的同时在结合概念会很清楚的理解了java线程池

12人推荐
随时随地看视频
慕课网APP