线程安全的ArrayList,加强版读写分离。
写有锁,读⽆锁,读写之间不阻塞,优于读写锁。
写⼊时,先copy⼀个容器副本、再添加新元素,最后替换引⽤。
使⽤⽅式与ArrayList⽆异。
示例:
public class TestCopyOnWriteArrayList {
public static void main(String[] args) {
CopyOnWriteArrayList list = new CopyOnWriteArrayList<>();
//创建一个线程池
ExecutorService pool= Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
pool.submit(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10; j++) {
list.add("content"+new Random().nextInt(100));
}
}
});
}
//关闭线程池
//4关闭线程池
pool.shutdown();
while(!pool.isTerminated()){
}
//5打印结果
System.out.println("元素个数:"+list.size());
for (String string : list) {
System.out.println(string);
}
}
}
CopyOnWriteArrayList使⽤了⼀种叫写时复制的⽅法,当有新元素添加到CopyOnWriteArrayList时,先从原有的数组中拷⻉⼀份出来,然后在新的数组做写操作,写完之后,再将原来的数组引⽤指向到新数组。
当有新元素加⼊的时候,如下图,创建新数组,并往新数组中加⼊⼀个新元素,这个时候,array这个引⽤仍然是指向原数组的。
CopyOnWriteArrayList 的整个add操作都是在锁的保护下进⾏的。 这样做是为了避免在多线程并发add的时候,复制出多个副本出来,把数据搞乱了,导致最终的数组数据不是我们期望的。
CopyOnWriteArrayList 的 add 操作的源代码如下:
public boolean add(E e) {
//1、先加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
//2、拷⻉数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
//3、将元素加⼊到新数组中
newElements[len] = e;
//4、将array引⽤指向到新数组
setArray(newElements);
return true;
} finally {
//5、解锁
lock.unlock();
}
}
由于所有的写操作都是在新数组进⾏的,这个时候如果有线程并发的写,则通过锁来控制,如果有线程并发的读,则分⼏种情况:
1、如果写操作未完成,那么直接读取原数组的数据;
2、如果写操作完成,但是引⽤还未指向新数组,那么也是读取原数组数据;
3、如果写操作完成,并且引⽤已经指向了新的数组,那么直接从新数组中读取数据。
可见, CopyOnWriteArrayList 的读操作是可以不用加锁的。
CopyOnWriteArraySet基于CopyOnWriteArrayList实现,其唯一的不同是在add时调用的是CopyOnWriteArrayList的addIfAbsent(若没有则增加)方法
它是线程安全的无序的集合,可以将它理解成线程安全的HashSet。有意思的是,CopyOnWriteArraySet和HashSet虽然都继承于共同的父类AbstractSet;但是,HashSet是通过“散列表(HashMap)”实现的,而CopyOnWriteArraySet则是通过“动态数组(CopyOnWriteArrayList)”实现的,并不是散列表。
和CopyOnWriteArrayList类似,CopyOnWriteArraySet具有以下特性:
1. 它最适合于具有以下特征的应用程序:Set 大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突。
2. 它是线程安全的。
3. 因为通常需要复制整个基础数组,所以可变操作(add()、set() 和 remove() 等等)的开销很大。
4. 迭代器支持hasNext(), next()等不可变操作,但不支持可变 remove()等 操作。
5. 使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照。
public class TestCopyOnWriteArraySet {
public static void main(String[] args) {
//1创建集合
CopyOnWriteArraySet set=new CopyOnWriteArraySet<>();
//2添加元素
set.add("pingguo");
set.add("huawei");
set.add("xiaomi");
set.add("lianxiang");
set.add("pingguo");
//3打印
System.out.println("元素个数:"+set.size());
System.out.println(set.toString());
}
}
初始容量默认为16段(Segment),使⽤分段锁设计。
不对整个Map加锁,⽽是为每个Segment加锁。
当多个对象存⼊同⼀个Segment时,才需要互斥。
最理想状态为16个对象分别存⼊16个Segment,并⾏数量16。
使⽤⽅式与HashMap⽆异。
示例:
public class TestConcurrentHashMap {
public static void main(String[] args) {
ConcurrentHashMap map = new ConcurrentHashMap<>();
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10; j++) {
map.put(Thread.currentThread().getName()+"--->"+j,j+"");
System.out.println(map);
}
}
}).start();;
}
}
}