ConcurrentHashMap:采用分段锁机制,来锁住并发集合,目前是16段锁,最多可以允许16个线程同时并发访问对象
CopyOnWriteArrayList:采用读不加锁而写加锁的方式,虽然写的时候影响了效率,但是并发读的时候效率非常高,不过可能产生脏数据,如果不影响的话,可以选择使用。
CopyOnWriteArraySet:同上
ArrayBlockingQueue:使用offer添加时,如果设置了超时时间,那么填满数组就等待指定的超时时间,如果没有设置超时时间,那么立马返回,不等待。使用put添加时,如果填满了会一直等待。
使用poll取出时,如果没有设置超时时间,那么如果数组为空,就直接返回空,否则取出元素。如果设置了超时时间,那么如果数组不为空,取出元素,如果已经为空,那么根据指定时间等待(若没有指定时间,直接返回)。
LinkedBlockingQueue:与上类似,不过采用链表实现,并且本身由于链表的特点,实际操作的应该是读取链表头的元素而从链表末尾加入数据,也就是读写操作不会冲突,所以可以考虑将put、offer与take、poll分隔开,采用不同的锁。所以高并发下使用LinkedBlockingQueue是比ArrayBlockingQueue更优的选择。
AtomicInteger:当要进行并发计数的时候,使用该对象会比同步拥有更好的性能。他的方法incrementAndGet中将操作获取当前值current,获取到+1之后的值next,并对两个值进行CAS比较(利用操作系统比较内存值和current值,如果相同表示正常,返回true,incrementAndGet直接返回next值;如果不同表示获取值出现异常,返回false,循环重复以上所有操作直到拿到正确的值)。
重点来啦!!!
ThreadPoolExecutor:
corePoolSize:线程池的基本大小,也就是没有任务执行的时候线程池的大小。当线程池中线程数量小于基本大小时,每次来一个任务,开一个线程执行。当线程数量超过核心池大小的时候,会吧多的任务放到缓存队列中,队列满了的话才继续创建线程。
maximumPoolSize:线程池中同时存在的线程最大数量。
newFixedThreadPool设置了固定的corePoolSize和maximumPoolSize(两者值相等,均为传递的参数)。并且该线程池不存在超时时间,所以不会自动回收闲置的线程。虽然固定了线程的数量,但是如果有多余指定数量的任务提交过来的时候,就会将任务放到阻塞队列LinkedBlockingQueue中等待执行。该线程池也是web服务器常用的一种线程池。
newSingleThreadPool是一个线程池大小为1的
固定线程池
,同时执行的任务数量只能为1个,多余的任务为扔到LinkedBlockingQueue等待执行。
newCachedThreadPool的corePoolSize为0,maximumPoolSize为最大整数(可以视为该线程池能够无限扩展),超时时间为1分钟,利用缓存队列SynchronousQueue(使用该队列是由于不这样做的话,直到工作队列满了才会增加线程,这通常不是我们想要的,所以利用该队列的话,一旦有任务加入进来,ThreadPoolExecutor检测到没有多余的线程的时候会马上先产生线程,来执行任务,而利用基本大小0作为回收线程的底线)。这样设置的目的是为了保证存在闲置线程并且空闲时间超过1分钟的时候,jvm能够最大化的回收这些线程,保证了性能的节约。但是他有个弊端就是,如果任务很多的时候,会产生很多线程,导致服务器超载。
备注:由于SynchronousQueue本质是将任务不断扔给一个新线程,所以这样在固定大小的线程池中就没有使用的意义了,因为,按照该队列的特性,长此以往,当任务数量超过线程池大小的时候,SynchronousQueue还欲求不满,需要新线程,而这样的需求又会因线程饱和而被拒绝,如此只会导致一系列的无意义动作。这也就是为什么将他用在一个可以无线扩充的线程池newCachedThreadPool的原因。
当任务之间存在依赖关系的时候,不适合使用固定大小的线程池,这样很容易因线程“饥渴”而导致死锁发生,为了避免该状况,一般会采用newCachedThreadPool。
newScheduledTheadPool:在该线程池出现之前,一般使用java.util.Timer进行延迟多少秒执行任务或者定时执行任务。
newScheduledTheadPool和Timer的比较:
前者可以使用多线程执行;后者只能单独一个线程执行定时或延时功能。
前者可以执行Callable任务,获取到任务返回值;后者由于采用的newThread直接启动的方式所以无法获取任务返回值。
前者一个线程抛出异常不影响其他线程任务;后者由于单线程,一个任务处异常,其他任务都执行不了。
而上述几个由doug lea提供好的几个线程池有一个共同的问题(当然newCachedThreadPool除外),就是当任务数量超过maximumPoolSize的时候,并且阻塞队列里面放满元素的时候,多余的任务怎么处理?
答案不言而喻,jdk肯定提供了一种机制来处理这些问题,这就是所谓的
饱和策略
。这些饱和策略为:
AbortPolicy(中止):默认饱和策略,一旦超过最大线程池数量,并且阻塞队列也放不下了,就会抛出线程。
DiscardPolicy(抛弃):会悄悄抛弃该任务
DiscardOldestPolicy(抛弃最旧):会抛弃下一个将被执行的任务,如果是优先队列,则会抛弃优先级最高的任务(加入队列的时间最长并且还没有被执行),所以该策略不要和优先队列一起使用
CallerRunsPolicy(调用者运行):将多余的任务回退给调用者,谁execute了该任务,就返回给该执行器。
参考文献:分布式java应用 基础与实践
java并发编程实战
java核心技术(一)