赞
踩
队列是一种先进先出的线性表结构,它的插入操作端为队尾,删除操作端为队头,在队列中插入一个元素称为入队,删除一个元素是出队。
支持阻塞插入方法:当队列满时,会阻塞向队列插入元素的线程,直到有元素出队
支持阻塞移除方法:当队列为空时,获取元素的线程会等待队列变为非空
阻塞队列常用于生产者消费者模式的场景,为了解决生产者和消费者处理效率不平衡的问题,通过阻塞队列来为生产者和消费者解耦,两者不直接通信,而是通过阻塞队列通信,生产者是向阻塞队列添加元素的线程,消费者是从阻塞队列拿元素的线程,阻塞队列相当于元素存放的容器。
有界阻塞列队:
无界阻塞列队:
缓存系统的设计:可以用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,表示缓存有效期到了。
LinkedTransferQueue 链表结构组成,有tryTransfer和tranfer方法:
transfer:如果当前有消费者正在等待接收元素,可以立刻把元素传递给消费者,如果没用则放在队列的tail节点,等该元素被消费才返回。
trytranfer:试探生产者传入元素是否能直接传递给消费者,如果没用消费者等待则返回false,而transfer必须等元素被消费者消费才返回
SynchronousQueue 不存储元素的阻塞队列,每一个put操作必须等待一个take操作,否正不能继续添加元素,相当于在双方中间递了一下元素
LinkedBlockingDeque 链表结构组成的双向阻塞队列,列队两端都可以插入和删除元素,多线程入队时减少了一半竞争
不管有界无界,都会产生阻塞,无界如果消费者拿元素为空,同样阻塞
无界并不是真正意义的无界,会受到系统资源的限制,处理不及时,内存超出界限,会出现OOM。
Excutor:是一个接口,是Excutor框架的基础,将任务的提交和执行分开
ExcutorService接口继承了Excutor,做了submit,shutdown扩展,算是真的下线程池接口
AbstractExcutorService抽象类,实现了ExcutorService接口中的大部分方法
ThreadPoolExecutor线程池的核心实现类,用来执行被提交的任务。
ScheduledExecutorService接口继承了ExcutorService接口,提供周期性执行ExcutorService
ScheduledThreadPoolExcutor是一个实现类,可以在确定的延迟后执行命令,或定期执行命令,比Timer更灵活,功能更强大。
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,
long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable>
workQueue,ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize核心线程数,prestartAllCoreThreads()方法会提前创建启动核心线程
maximumPoolSize,最大线程数
设置技巧
在创建线程池时,可以先考虑是用来执行什么样的任务
cpu密集型(计算):核心线程数 = cpu内核数+1,+1是在使用虚拟内存时最大化使用cpu
I/O密集型(网络/本地读写数据):cpu内核数*2
混合型:前面两种任务数相等最好分开,或者cpu内核数*2
keepAliveTime,线程空闲时的存活时间,默认只在大于核心线程时生效
TimeUnit,是keepAliveTime的时间单位
workQueue,必须是BlockingQueue阻塞队列,当超过核心线程数时候,会进入阻塞队列等待,这样线程池实现了阻塞功能。
尽量使用有界队列,使用无界会因为无限制往队列添加任务,导致maximumPoolSize,keepAliveTime,TimeUnit都成为无效参数,还有资源耗尽的风险
threadFactory,创建线程的工厂,通过自定义工厂,可以给新建的线程设置名称,或设置为守护线程等。Executors静态工厂里默认的threadFactory,线程的命名规则是“pool-数字-thread-数字”。
RejectedExecutionHandler,线程饱和的策略,当队列满了,且没用空闲工作线程时,提供了四种策略:
bortPolicy:直接抛出异常,默认策略;
CallerRunsPolicy:用调用者所在的线程来执行任务;
DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
DiscardPolicy:直接丢弃任务;
线程池的工作机制:
提交任务
execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。
submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
关闭线程池
可以通过调用线程池的shutdown或shutdownNow方法关闭线程池。原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。但是它们存在一定的区别,shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,而shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。
只要调用了这两个关闭方法中的任意一个,isShutdown方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。至于应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown方法来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow方法。
线程池配置策略
首先分析任务的特性:
CPU密集型:核心线程数 = cpu内核数+1,+1是在使用虚拟内存时最大化使用cpu
IO密集型:cpu内核数*2,IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程
混合型:如果可以拆分,将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。如果这两个任务执行时间相差太大,则没必要进行分解。可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数
优先级不同的任务可以使用优先级列队PriotyBlockingQueue处理。
执行时间不同的任务可以交给不同规模的线程池来处理,或者可以使用优先级队列,让执行时间短的任务先执行。
建议使用有界队列。有界队列能增加系统的稳定性和预警能力,可以根据需要设大一点儿,比如几千。
使用有界队列
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。