1. JUC 简介
我们来看看在 Java 5.0 提供了 java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常用的工具类,用于定义类似于线程的自定义子系统,包括线程池,异步 IO 和轻量级任务框架;还提供了设计用于多线程上下文中的 Collection 实现等
2.常用集合
我们之前了解的集合大多是线程不安全的,比如说ArrayList,HashSet,HashMap,但它们往往高效率;
也有一些线程安全的集合,如Vector,HashTable,但大多都是基于sychronized锁控制机制,性能很低。
如果既保证线程安全,执行效率又高,就可考虑下JUC下的集合类了,如:
-
ArrayList对应的高并发类是CopyOnWriteArrayList,
-
HashSet对应的高并发类是 CopyOnWriteArraySet,
-
HashMap对应的高并发类是ConcurrentHashMap。
CopyOnWriteArrayList其中的add方法:
public boolean add(A a) {
final ReentrantLock lock = this.lock;
lock.lock();//加锁
try {
Object[] elements = getArray();//创建了一个新数组
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);//copy了旧数组,并将数组长度增加一
newElements[len] = a;//将a添加到新数组中
setArray(newElements);//将修改后的数据替换原有的数据
return true;
} finally {
lock.unlock();
}
}
可以看到当执行写操作时,先copy一份原有数据数组,再对复制数据进行写入操作,最后将复制数据替换原有数据,从而保证写操作不影响读操作。
同时,其复制的整个过程是上了锁的,所以不会产生多线程安全问题。
而加的锁 ReentrantLock 相比 Syncchronized 具有以下功能:
-
等待可终止:
如果一个线程长期占有锁对象不释放,那么等待的线程可以选择停止等待,这样也避免了死锁的发生; -
公平锁:等待的线程必须按照申请的时间顺序去持有锁对象
-
并不像synchronized那样要么唤醒一个线程,要么唤醒所有线程
,实现了可以分组唤醒需要唤醒的线程
CopyOnWriteArraySet与CopyOnWriteArrayList类似,只不过前面的数据无序,不可重复
接下来我们来看看 ConcurrentHashMap。
ConcurrentHashMap实现了HashTable的所有功能,线程安全,但却在检索元素时不需要锁定,因此效率更高。
之前是分段锁的思想,通过采用分段锁Segment减少热点域来提高并发效率。
1.8之后利用CAS+Synchronized来保证并发更新的安全,底层采用数组+链表+红黑树的存储结构。
ConcurrentHashMap 是设计为非阻塞的。就是一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。
在更新时会局部锁住某部分数据,但不会把整个表都锁住。同步读取操作则是完全非阻塞的。好处是在保证合理的同步前提下,效率很高。
ConcurrentHashMap 采用”分段锁“ 的方式:
- HashTable容器在竞争激烈的并发环境下表现出效率低下的原因,是因为所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据。
- 那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术。
- 首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。