并发容器
- ConcurrentHashMap:数据安全的HasnMap。
- CopyOnWriteArrayList:数据安全的List。
- BlockingQueue:这是一个接口,表示一个阻塞队列,非常适合做数据交互通道。
- ConcurrentLinkedQueue:高效的非阻塞并发队列,使用链表实现。可以看做一个线程安全的LinkedList。
- ConcurrentSkipListMap:是一个Map,使用跳表的数据结构进行快速查询。
- 为什么HashMap是线程不安全的?
- 同时put碰撞导致数据丢失。
- 同时put扩容导致数据丢失。
- 死循环造成CPU100%(JDK7以及以前会出现,原理:在多线程同时扩容的时候,产生循环链表,导致CPU100%)。
1.7里的HashMap结构
1.8里HashMap的结构
ConcurrentHashMap和CopyOnWriteArrayList取代同步的HashMap和同步的ArrayList。绝大多数情况下ConCurrenthashMap和CopyOnWriteArrayList性能更好。
HashMap只读是安全的,如果并发环境非要用HashMap的话,就用Collections.synchronizedMap(new HashMap())
JDK1.7的ConcurrentHashMap实现与分析
- JAVA7 ConcurrentHashMap最外层是多个segment,每个segment底层与HashMap类似,仍然是数组与链表组成的拉链法。
- 每个segment独立上ReentrantLock锁,每个segment之间互不影响,提高了效率。
- ConcurrentHashMap默认有16个segment,最多可以支持16个线程的并发(操作分别分布在不同的segment上)。这个默认值可以再初始化的时候设置为其他值,但是初始化完成后,是不可以扩容的。
JDK1.8的ConcurrentHashMap
- 结构和1.8的HashMap差不多
- 当冲突达到8的时候,链表会转为红黑树。源码注释里面有说明(出现8的概率非常小,转为红黑树的目的就是处理这种极端情况下查询慢的问题,因为链表短的时候,对速度影响还可以接受)。
- 为什么达到8才转,不直接使用红黑树,是因为,红黑树的每个节点占用空间是链表的两倍。
- CAS + synchronized实现线程安全。
* first values are:
*
* 0: 0.60653066
* 1: 0.30326533
* 2: 0.07581633
* 3: 0.01263606
* 4: 0.00157952
* 5: 0.00015795
* 6: 0.00001316
* 7: 0.00000094
* 8: 0.00000006
* more: less than 1 in ten million
- CopyOnWriteArrayList
- 代替Vector和SynchronizedList。
- Vector和Synchronized的锁的粒度太大,并发效率相对过低,并且迭代时无法编辑。
- Copy-On-Write容器还包括了CopyOnWriteArraySet,用来代替同步Set。
- 适用场景:
- 读操作尽可能的快,而写即使慢一点也没关系。
- 读多写少:黑名单,每日更新;监听器:迭代操作远多于修改操作。
- 读写规则:
- 同读写锁:读读共享,其他都互斥。
- 读写锁规则的升级:读取时是完全不用加锁的,而且写入也不会阻塞读操作。只有写入与写入之间需要同步等待。
- 实现原理: 创建新的副本、读写分离。
- 缺点:
- 数据一致性问题:CopyOnWrite只能保证数据最终一致性,不能保证实时一致性,如果你希望写入的数据马上被读到,就不要使用CopyOnWrite容器。
- 内存占用问题:因为CopyOnWrite写是复制机制,所以在进行写的时候,内存里会驻扎两个对象的内存。
- 阻塞队列(BlockingQueue):
- 阻塞队列是具有阻塞功能的队列。
- 通常阻塞队列的一端是给生产者放数据用,另一端给消费者拿数据用。阻塞队列是线性安全的,所以生产者消费者都可以是多线程。
- 最具有特色的两个具有阻塞功能的方法:
- take():获取并移除队列的头结点,一旦如果执行take的时候,队列里无数据,则阻塞,直到队列里有数据。
- put():插入数据,但是如果队列已满,那么无法继续插入,则阻塞,直到队列里有了空闲空间。
- 是否有界(容量大小):这是一个非常重要的属性,无界队列意味着里面可以容纳非常多(Integer.MAX_VALUE,约为2的31次方是一个非常大的数,近似任务无限容量)
- 阻塞队列是线程池的重要组成部分。
- 主要方法:put、take;add、remove、element;offer、poll、peek。
- ArrayBlockingQueue:
- 有界
- 指定容量
- 公平:还可以指定是否需要保证公平,如果想保证公平,那么等待了最长时间的线程会被优先执行,不过这会带来一定得性能损耗。
- PriorityBlockingQueue:
- 支持优先级
- 自然顺序(而不是先进先出)
- 无界队列
- PriorityQueue的线程安全版
- LinkedBlockingQueue:
- 无界
- 容量:Integer.MAX_VALUE
- 内部结构:node、两把锁
- SynchronousQueue:
- 容量0
- 需要注意的是SynchronousQueue的容量不是1而是0,因为SynchronousQueue不需要持有元素,它所有做的是直接传递。
- 效率很高。
- 是一个极好的用来直接传递的并发数据结构。
- SynchronousQueue是线程池Executors.newCachedThreadPool()使用的阻塞队列。
-
DelayQueue:
- 延迟队列,根据延迟时间排序。
- 元素需要实现Delayed接口,规定排序规则。
-
非阻塞并发队列:
- 并发包中非阻塞并发队列只有ConcurrentLinkedQueue这一种,顾名思义ConcurrentLinkedQueue是使用链表作为其数据结构的,使用CAS非阻塞算法来实现线程安全(不具备阻塞功能),适合用在性能要求较高的并发场景。用的相对较少。