java中创建线程池的核心是使用ThredPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue)
(1)当一个任务到来的时候如何它的核心线程数即corePoolSize没满的话,就会创建一个核心线程去执行该任务
(2) 如何核心线程数满了,并仍有任务提交,则将任务先放入阻塞队列中
(3) 阻塞队列已无空闲,还有任务继续提交,若最大线程数没有满的话,则会新建非核心线程并分配任务
如果核心线程数、阻塞队列、最大线程数都满了的话,就会执行线程池的拒绝策略,一共有4种方式:
(1)直接丢弃任务
(2)丢弃任务并抛出异常(默认)
(3) 将阻塞队列的头节点丢弃,然后尝试将任务放入队列中
(4) 将任务交由主线程即调用者来执行该任务
核心线程是否被回收
在ThreadPoolExecutor类中有一个属性叫 allowCoreThreadTimeOut 默认是false,即核心线程被创建后不会被回收,可以通过它的同名方法将其置为true,此时当核心线程闲置超过空闲时间keepalive后将会被回收掉
核心线程何时被创建
默认情况下当任务到来时才会创建核心线程,不过ThreadPoolExecutor中有两个方法可以提前创建核心线程,一个是preStartAllCoreThread()它会启动所有核心线程,另一个是preStartCoreThread(),这一个的返回值是boolean类型,如果所有的核心线程均已被启动则会返回false,如果一个核心线程启动成功将返回true。
另外需要指出的是,当任务到来时,如果存活的核心线程数小于corePollSize的数量,就算现在有核心线程处于空闲状态,也会新建一个核心线程去执行任务,不会把任务分配给空闲线程;当存活线程数大于corePoolSize时,将任务放入等待队列;在使用有界队列时(为什么单单指出是有界队列,因为无界队列是装不满的,像FixedThreadPool,SingleThreadPool,使用的阻塞队列是LinkedBlockingQueue,可以设置该队列的长度,在这两种线程池创建的过程中长度默认为Integer.Max_Value,是无界的,他们的核心线程数和最大线程数相等,新任务到来时,如果所有核心线程都被创建,则直接进入阻塞队列等待被执行),若当前线程数大于等于corePoolSize,但小于maxPoolSzie,此时会把任务放到等待队列中,而不会新建一个线程去执行它,除非此时等待队列已经满了,才会创建一个新线程去执行该任务,如果阻塞队列已满且当前存活线程数为maxPoolSize,则会执行拒绝策略。
拒绝策略的发生条件有两种,除了上面说的,当线程池关闭时,新提交的任务也会触发拒绝,有4种,默认是丢弃任务并抛出异常。
AbortPolicy:默认测策略,丢弃任务并抛出RejectedExecutionException运行时异常;
CallerRunsPolicy:由提交任务的线程执行该任务,并通过反馈机制,减慢提交新任务的速度;
DiscardPolicy:直接丢弃新提交的任务;
DiscardOldestPolicy:如果执行器没有关闭,队列头的任务将会被丢弃,然后执行器重新尝试执行任务(如果失败,则重复这一过程)
阻塞队列有3种类型:
直接提交:CacheThreadPool 的阻塞队列为SynchronousQueue<Runable>,该阻塞队列不存储任务,当任务到来时,调用put方法想将任务放入队列,如果当前没有线程使用take()方法获取任务的话,那么执行put操作的线程将陷入等待,直到有线程调用take(),反之亦然,有线程使用take()相要从队列中获取任务,但是没有线程使用put放入任务,那么它也会阻塞。因此使用该类型的阻塞队列时,maxPoolSize需要使用Integer.max_value,用来避免阻塞任务提交,但是当任务的平均提交速度大于任务的平均执行速度时,会出现线程数会无限增长的问题;
无界队列 :SingleThreadPool、FixedThreadPoll 使用的是LinkedBlockingQueue<Runable>,使用该值时,当核心线程都创建完毕后,之后到来的任务都会进入阻塞队列中等待执行,导致maxPoolSize无意义,一般与corePoolSize相等即可,它比较适合于互不影响、相互独立的任务,如web等。但是当任务的平均提交速度大于平均执行速度时,会出现队列无限增长的问题。
ScheduledThreadPool它的阻塞队列为DelayedWorkQueue 是一个基于优先级无界阻塞队列,会将距离可执行时间最短的任务放到队列的头部,优先级高
有界队列 如ArrayBlockingQueue,并配合有限的maxPoolSize来控制资源的消耗,但很难控制。使用较大的队列,较小的corePoolSize,可以减少cpu使用率,任务的上下文切换,但是会造成任务的阻塞;使用较小队列,较大的corePoolSize,会使cpu更繁忙。
不管是SynchronousQueue、ArrayBlockingQueue还是LinkedBlockingQueue或DelayedWorkQueue它们都实现了BlockingQueue阻塞队列接口。先进先出,支持泛型
它是线程安全的,使用ReentrantLock加锁,当调用它的put或take方法时都需要获取锁。
它的核心方法分为如下几类
动作种类 | 抛出异常 | 返回特定的值 | 等待 | 超时 |
插入节点 | add(o) | offer(o) | put(o) | offer(o, time, timeUnit) |
删除节点 | poll()、remove(o) | take() | poll(time, timeUnit) | |
检查 | peek(o) |
抛出异常:add方法,当阻塞队列为有界且其中元素已满时,抛出异常
返回特定的值:offer 当队列已满时,插入失败返回false、成功返回true;poll和remove为删除队列头节点和指定节点,当队列为空remove返回false,poll返回null,否则分别返回true和元素E;peek获取队列头节点,队列为空时返回null,否则返回E;
等待:put将元素插入队列队尾,当队列已满时,陷入等待,当队列不满notFull condition满足时,被唤醒继续进行插入;take()弹出队列头节点,当队列为空时,陷入等待,当队列不空notEmpty condition满足时被唤醒,继续执行。
超时::如果操作不能马上进行,操作会被阻塞指定的时间,如果指定时间没执行,则返回一个特殊值,一般是true或者false
ArrayBlockingQueue和LinkedBlockingQueue的区别:
1.ArrayBlockingQueue中所有的操作都是使用同一个ReentrantLock锁,且lock可以指定是公平锁还是非公平锁,默认是非公平锁;LinkedBlockingQueue中有两个锁都是非公平的,一个是takelock一个是putkock,它们分别用于获取头节点和插入的操作,这样能大大提高吞吐量,当高并发的情况下,生产者和消费者能并行操作队列中的数据,提高并发性能。
2.ArrayBlockingQueue是有界的,创建时需要指定长度;LinkedBlockingQueue既可以是无界的又可以是有界的,默认长度是max,无界
3.ArrayBlockingQueue是使用数组存储元素节点,LinkedBlockingQueue是使用node节点链表的形式
阻塞队列BlockingQueue及其子类的使用_Lyzxii的博客-CSDN博客_queue子类
https://www.jianshu.com/p/c41e942bcd64 有个图
https://www.cnblogs.com/drizzlewithwind/p/7707471.html