一、阻塞队列BlockingQueue
1、阻塞队列支持阻塞的插入和移除方法;当队列满时继续插入元素会产生阻塞,当队列为空时继续进行获取元素也会阻塞对应的线程;
2、阻塞队列很好的协调了生产者和消费者之间的关系,解决了生产者生产速度和消费者消费速度不平衡的问题,当生产者生产完数据之后放入阻塞队列,然后消费者从阻塞队列中取出数据进行消费;将生产者和消费者解耦,平衡了生产者和消费者的处理能力;
3、BlockingQueue中定义了三对插入数据和移除数据的方法:
add和remove不会产生阻塞,而是会直接抛出异常;
offer和poll会返回特殊值,offer成功返回true,队列为空时poll返回null; offer和poll还支持延时阻塞存取;
put和take才是阻塞队列中能产生阻塞的核心方法;
常用的阻塞队列
ArrayBlockingQueue:由数组结构组成的有界阻塞队列
我们看名字也知道其底层数据结构是数组,我们知道ArrayList和HashMap的底层实现也是数组,但它们三者具体的底层实现是不同的;我们以新增元素举例,ArrayList是通过size++来找到对应的索引,HashMap是通过计算元素的哈希值,然后再计算在数组中的索引,如果此索引处存在元素了,也就是发生了Hash冲突,是通过链表或者红黑树解决的;而ArrayBlockingQueue的底层类似是一个循环数组,数组大小一旦确定,不能改变,维护了一个放数据的索引和一个取数据的索引,通过两个索引存取数据;
存取数据时首先加锁,锁使用的是ReentrantLock,然后当取元素时如果队列为空,就使用notEmpty.await进行阻塞等待,如果成功取到元素就调用notFull.signal通知;当放元素的时候如果队列满了,就使用notFull.await进行阻塞等待,成功放入之后调用notEmpty.signal通知;这里的notEmpty和notFull都是ReentrantLock的条件变量对象Condition;
实现队列时必须指定大小,此队列按照先进先出的规则进行排序,默认情况下不能保证线程公平的访问队列,我们可以在构造函数中传入true使的内部实现变成可重入的公平锁ReentrantLock;所谓公平访问是指先阻塞的线程优先访问队列;此队列中的锁是没有分离的,即生产和消费用同一把锁;
LinkedBlockingQueue:有链表结构组成的有界阻塞队列
默认最大长度为Integer.MAX_VALUE,也是按照先进先出的规则;队列中锁是分离的,生产用putLock,消费用takeLock,两把锁的目的就是存和取可以同时进行;
阻塞也是通过Condition实现的:
当put元素时,如果队列满,调用notFull.await阻塞等待,如果队列没满,直接添加到链表尾,然后更新一下count,如果添加完还没满,再调用一下notFull.signal通知一下其他阻塞等待添加的线程;最后如果count == 0,意思就是原来链表中没有元素,但是执行了put之后链表中有一个元素了,调用notEmpty.signal通知一下阻塞take的线程;
当take元素时,如果队列为空,调用noEmpty.await阻塞等待,如果队列不为空,直接从链表头部取出元素,然后更新一下count,如果take完之后还有元素,调用一下notEmpty.signal通知一下其他阻塞等待take的线程;最后如果count == capacity,意思就是原来队列是满的,我们执行完这一次take之后不是满的了,调用一下notFull.signal通知一下阻塞put的线程;
我们可以看到put和take中的阻塞是相互协同工作的;offer、poll超时阻塞的原理基本一致,就是修改了一点计时的逻辑;
PriorityBlockingQueue:支持优先级排序的无界阻塞队列
默认情况下元素采用自然顺序升序排列,可以在构造函数中传入一个Comparator对象,通过自定义的compare方法对元素进行排序;
DelayQueue:使用优先级队列实现的无界阻塞队列
此队列使用PriorityQueue实现,队列中的元素必须实现Delayed接口,创建元素时指定多久才能从队列中取出元素;常用于缓存系统的设计;
SynchronousQueue:不存储元素的阻塞队列
每一个put操作必须等待一个take操作,否则不能继续添加元素;
LinkedTransferQueue:由链表组成的无界阻塞队列,多了transfer和tryTransfer方法
如果当前有消费者正在等待接受元素,transfer可以把生产者传入的元素立刻传输到消费者,如果没有消费者等待,则会将元素放入队列中的tail节点,并一直等待直到消费者消费了才返回;tryTransfer的区别时无论消费者是否接受,都立即返回,如果没有消费者接受返回false;
LinkedBlockingDeque:由链表组成的双向阻塞队列
可以从两端插入和移除元素,多了一个入口,在多线程入队出队时也就减少了一般的竞争;内部的插入和移除方法中基本扩展为了addFirst、addLast这种结构的方法;
二、线程池ExecutorThreadPool
1、为什么要使用线程池
降低资源的消耗;提高响应的速度;提高线程的可管理性;
2、继承结构
Executor--ExecutorService--AbstractExecutorService--ThreadPoolExecutor
ExecutorService--ScheduledExecutorService--ScheduledThreadPoolExecutor;ScheduledThreadPoolExecutor可以在给定的延迟之后执行任务或者定期执行任务,比Timer更灵活,功能更强大;
3.线程池创建各个参数含义
corePoolSize:核心线程数;如果执行了preStartAllCoreThreads方法,线程池会提前创建并启动所有核心线程;
maximumPoolSize:最大线程数;
keepAliveTime:空闲线程的存活时间;默认情况下只有线程大于corepoolsize才有效;
TimeUnit:keepAliveTime的时间单位
workQueue:阻塞队列;尽量使用有界阻塞队列
threadFactory:线程工厂,一般用来设置线程名或者设置守护线程等;
RejectedExecutionHandler:饱和策略,或称之为拒绝策略
- DiscardOldestPolicy:丢弃最早的任务;
- AbortPolicy:抛出异常;
- CallerRunsPolicy:让提交任务的线程自己处理;
- DiscardPolicy:直接丢弃;
4、线程池的工作机制
如果当前线程小于核心线程数,首先创建线程执行任务;如果运行的线程大于等于核心线程数了,就会将提交的任务放入阻塞队列;如果阻塞队列也满了,并且运行的线程小于最大线程数,则会创建线程;如果运行的线程等于最大线程数了,再提交任务时执行拒绝策略;
5、提交任务
execute方法用于不需要返回值的任务; submit提交需要返回值的任务,返回Future类型的对象;可以通过get方法来获取返回值,调用get方法会阻塞当前线程直到任务完成;
6、关闭线程池
shutdown:中断所有没有正在执行的线程;
shutdownNow:尝试中断所有线程;
这两个方法的原理都是遍历线程池中的线程,然后调用interrupt方法尝试中断线程,所以那些没有响应中断的任务可能并不能终止; 调用这两个方法之后,isShutdown方法就会返回true;当所有的任务都已关闭时才表示线程池关闭成功,此时isTerminated返回true;
7、合理的配置线程池
cpu密集型任务:一般设定最大线程数为cpu核心数或者+1或者+2;
io密集型任务:一般设定两倍的cpu核心数;
8、ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,增加了延迟执行任务和定时循环执行任务的方式;
schedule方法,可提交Runnable或者Callable任务,指定延迟时间后执行一次,返回ScheduledFuture类型的对象,ScheduledFuture继承自Future,用于获取任务结果或者取消任务;
scheduleAtFixedRate:可提交Runnable类型任务,指定初始延迟后执行第一次,然后在指定时间间隔下循环运行;返回值为ScheduledFuture,可用于取消任务;
scheduleWithFixedDelay:可提交Runnable类型任务,指定初始延迟后执行第一次,然后在任务执行完毕之后经过时间间隔依次循环运行;返回值为ScheduledFuture,可用于取消任务;ScheduledFuture仅仅继承自Future和Delayed两个接口,并且啥都没扩展;