千里之行始于足下之(三)线程池

298 阅读6分钟
You have a dream, you got to protect it. People can't do something by themselves; they wanna tell you you can not do it. You want something. Go get it!

先想是不是,再问为什么!

上一篇文章中提到有关线程池的一些基本概念,本文将不再赘述,本文着力于使用代码来解读相关线程池知识。

虚拟机设置


线程池执行任务逻辑和线程池参数的关系


Executors创建线程池的方式

根据返回对象类型创建线程池可以分成以下几类:

创建返回ThreadPoolExecutor对象

创建返回ScheduleThreadPoolExcutor对象

创建返回ForkJoinPool对象

Executors创建返回ThreadPoolExecutor对象

newCachedThreadPool(创建可缓存的线程池)



创建线程参数:

核心线程数 corePoolSize: 0 

最大线程数maximumPoolSize:Integer.MAX_VALUE 

非核心线程空闲存活时间keepAliveTime:60

时间单位 unit:  SECONDS

线程池所用缓冲队列workQueue:SynchronousQueue

线程默认工厂、默认处理器

SynchronousQueue

SynchronousQueue(同步队列)一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,newCachedThreadPool线程池使用了这个队列。

public SynchronousQueue() {
        this(false);
    }
public SynchronousQueue(boolean fair) {
        transferer = fair ? new TransferQueue<E>() : new TransferStack<E>();
    } 

static class DefaultThreadFactory implements ThreadFactory {

        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }
   /**
     * The default rejected execution handler
     */
    private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
//拒绝策略默认使用AbortPolicy,直接抛出RejectedExecutionException异常。
 public static class AbortPolicy implements RejectedExecutionHandler {
        public AbortPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }

当一个任务提交时,不创建核心线程,创建非核心线程来处理;核心线程数量非常大,可当作无限创建线程,空闲非核心线程回收时间为60秒,在之前文章说明创建线程是需要消耗内存资源的,因此在有限的资源情况下是非常容易报OOM异常。



newSingleThreadExecutor(创建单线程的线程池)


测试创建线程参数:

核心线程数 corePoolSize: 1

最大线程数maximumPoolSize:1

非核心线程空闲存活时间keepAliveTime:0

时间单位 unit: MILLISECONDS

线程池所用缓冲队列workQueue:LinkedBlockingQueue

LinkedBlockingQueue

LinkedBlockingQueue(可设置容量队列)基于链表结构的阻塞队列,按FIFO排序任务,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长Integer.MAX_VALUE,newSingleThreadExecutor线程池使用了这个队列。

 public LinkedBlockingQueue() {
        this(Integer.MAX_VALUE);
    }
 public LinkedBlockingQueue(int capacity) {
        if (capacity <= 0) throw new IllegalArgumentException();
        this.capacity = capacity;
        last = head = new Node<E>(null);
    }

测试中设置1个核心线程,没有空闲时间,当一个任务提交时,有线程执行?有将任务放到阻塞队列:没有 创建一个线程,没有空闲时间,使用有界阻塞列表(可看作无限),执行完当前线程就从队列取任务,执行完一个,再继续取,(一条线程)夜以继日地干活。如果任务的执行比较耗时,将会导致队列的任务越积越多,效率不高。


newFixedThreadPool(创建固定长度线程池)



测试创建线程参数:

核心线程数 corePoolSize: 5

最大线程数maximumPoolSize:5

非核心线程空闲存活时间keepAliveTime:0

时间单位 unit: MILLISECONDS

线程池所用缓冲队列workQueue:LinkedBlockingQueue


测试中设置5核心个线程,没有空闲时间,当一个任务提交时,在已经创建核心线程执行,没有空闲时间,使用有界阻塞列表(可看作无限),如果任务的执行比较耗时,将会导致队列的任务越积越多,导致内存使用不停飙升, 最终导致OOM异常。



Executors创建返回newScheduledThreadPool对象

newScheduledThreadPool(定时及周期执行的线程池)




测试创建线程参数:

核心线程数 corePoolSize: 5

最大线程数maximumPoolSize:Integer.MAX_VALUE

非核心线程空闲存活时间keepAliveTime:0

时间单位 unit: NANOSECONDS

线程池所用缓冲队列workQueue:DelayQueue

DelayQueue

DelayQueue(延迟队列)是一个任务定时周期的延迟执行的队列,根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool线程池使用了这个队列。

测试中设置5核心个线程,没有空闲时间,当一个任务提交时,创建核心线程执行,执行当前线程从延时队列中取任务,线程从 延时队列中获取 time 大于等于当前时间的task,执行完后修改这个 task 的 time 为下次被执行的时间,如果当前核心线程已经满了,将任务放置到延时队列。

创建返回ForkJoinPool对象

暂时未曾使用过,待笔者使用后,将会补充这一空白区域!

小结

分析完上面四种可知:允许的任务队列长度为Integer.MAX_VALUE,就可能会有大量任务堆积在队列中,即而可能引起OOM异常;允许创建的线程数为Integer.MAX_VALUE,就可能会创建大量的线程,即而可能引起OOM异常;不设置多核心线程,一线程工作效率太低。

拒绝策略

AbortPolicy(抛出一个异常,默认的)

DiscardPolicy(直接丢弃任务)

DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池)

CallerRunsPolicy(交给线程池调用所在的主线程进行处理)

自定义拒绝策略,需要实现RejectedExecutionHandler接口


将创建返回ThreadPoolExecutor对象的线程池比作社区诊所、区卫生所、区医院,那么就可以很直观的发现;诊所只有一个医生,诊所内有一张长凳;区卫生所,有固定多个医生值班,卫生所内有一排排长椅;区医院门诊部,有多个医生值班,当病人很多时候还可以再请求科室医生下来再使用几个诊室,门诊候诊区外面有很多的长椅。当医院门诊部接收到医院内部通知,院内所有床位已用尽,等候区也已经饱和。这时开始执行拒绝措施:要么将问题不大的病人办理出院,空出位置;要么在门诊门口贴出告示,请病人到其他医院就诊,本医院已经饱和;要么直接不接收新的病人,让救护中心不要再送病人入院;要么暂时关闭门诊部,不再运行门诊,等到院内资源释放,再运作。


本文要是有遗漏的地方,我将在评论区给出,同时若读者发现本文不当之处也请在评论区指出,共同研究。

特别注意:本文摘抄文字版权属于原作者!!!