Java并发容器和Java线程池
ConcurrentHashMap
ConcurrentHashMap是线程安全且高效的HashMap
为什么要用ConcurrentHashMap呢?
因为在并发编程中使用HashMap可能会导致程序死循环,而使用线程安全的HashTable效率又非常低下
-
HashMap
在多线程环境下,因为有并发状况,所以不能使用HashMap。HashMap在并发执行put操作时会引起死循环,这是因为多线程会导致HashMap的Entry链表形成环形数据结构
-
HashTable使用synchonrized来保证线程安全,所以效率低下
-
ConcurrentHashMap的锁分段技术可有效提高并发访问率
ConcurrentHashMap的结构
1.8中加入了红黑树,树查询的效率更高
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成
Segment是一种可重入锁,在ConcurrentHashMap里扮演锁的角色
HashEntry则用于存储键值对数据,是一种数组和链表结构
要对HashEntry数组的数据进行修改首先要获得对应的Segment锁
ConcurrentHashMap操作
ConcurrentHashMap的三种操作get,put和size
-
get操作
实现非常简单,先经过一次再散列,然后使用这个散列值通过散列运算定位到Segment,再通过散列算法定位到元素。
高效之处时整个get操作不需要加锁,除非读到的值是空才会加锁重读。因为它的get方法将要使用的共享变量都定义成volatile类型,能够再线程之间保持可见性,能够被多线程同时读,并且保证不会读到过期的值
-
put操作
在操作共享变量时必须加锁。put方法首先定位到Segment,然后在Segment里进行插入操作。插入操作需要经历两个步骤:第一步是判断是否需要对Segment里的HashEntry数组进行扩容,第二步定位添加元素的位置,然后将其放在HashEntry数组里
-
size操作
统计所有Segment里元素的大小后求和
Java中的阻塞队列
如果要实现一个线程安全的队列有两种方式:一种是使用阻塞算法,另一种是使用非阻塞算法。
使用阻塞算法的队列可以用一个锁或两个锁实现
使用非阻塞算法的队列则可以使用循环CAS的方式来实现
什么是阻塞队列?
阻塞队列是一个支持两个附加操作的队列,这两个附加的操作支持阻塞的插入和移除方法
- 支持阻塞的插入方法:当队列满时,队列会阻塞插入元素的线程,直到队列不满
- 支持阻塞的移除方法:在队列为空时,获取元素的线程会等待队列变成非空
阻塞队列常用于生产者和消费者的场景,生产者存放元素,消费者获取元素
- BlockingQueue阻塞队列
- 阻塞队列是局由自动阻塞功能的队列,线程安全;take方法移除队头,若队列无数据则阻塞直到有数据;put方法插入元素,如果队列已满就无法继续插入则阻塞直到队列里有了空闲空间
- ArrayBlockQueue
- 有界可指定容量、可公平
- Put源码加锁,可中断的上锁方法。没满才可以入队,否则一直await等待。
- LinkedBlockingQueue
- 无界容量为MAX_VALUE,内部结构Node
- 使用了两把锁take锁和put锁互补干扰
- PriorityBlockingQueue
- 支持优先级,无界队列
- SynchronousQueue
- 直接传递的队列,容量0,效率高线程池的CacheExecutorPool使用其作为工作队列
- DelayQueue
- 无界队列,根据延迟时间排序
线程池
好处:
- 降低资源消耗
- 提高响应速度
- 提高线程的可管理性
实现原理
- 线程池判断核心线程池是否都在执行任务,如果不是,则创建一个新的工作线程来执行任务,如果核心线程池里的线程都在执行任务,则进入到下一个流程
- 线程池判断工作队列是否已经满。如果队列没有满,则将新提交的任务存储在这个工作队列里,如果工作队列满,则进入下一个流程
- 线程池判断线程池的线程是否都处于工作状态,如果没有,则创建新的工作线程来执行任务,如果已经满了,则交给饱和策略来处理这个任务
线程池的使用
线程池的创建
需要输入几个参数
- corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建了
- runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列,可以选择上面列出的阻塞队列
- maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数
- ThreadFactory:用于创建线程的工厂
- RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采用一种策略处理提交的新任务
向线程池提交任务
- execute():用于提交不需要返回值的任务,所以无法判断是否执行成功
- submit():用于提交需要返回值的任务
关闭线程池
使用shutdown或shutdownNow来关闭线程池。他们的原理是遍历线程池的工作线程,然后组个嗲用线程的interrupt方法来中断线程
线程池的监控
- taskCount:线程池需要执行的任务数量
- completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount
- largestPoolSize:线程池里曾经创建过的最大线程数量
- getPoolSize:线程池的线程数量
- getActiveCount:获取活动的线程数