JUC并发容器1(CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentSkipListSet)
CopyOnWriteArrayList
ArrayList对应的线程安全的并发容器是CopyOnWriteArrayList HashSet对应的线程安全的并发容器是CopyOnWriteArraySet CopyOnWriteArraySet与CopyOnWriteArrayList类似,CopyOnWriteArraySet底层还是调用的CopyOnWriteArrayList的方法。
写操作的步骤
1.先拷贝一份原来的list 为 listNew
2.在新的数组listNew上做写操作
3.写完后将原来的数组list 指向新的数组listNew
这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器是一种读写分离的思想,读和写不同的容器、最终一致性以及使用另外开辟空间的思路,来解决并发冲突的思想。
CopyOnWriteArrayList的add操作都是在锁的保护下进行的,避免多线程并发做写操作时复制出多个副本。
读操作
是在原数组上读,不需要拷贝。
缺点
1.由于写操作要拷贝数组,会消耗内存。如果数组比较大,可能会导致young GC或者 full GC。 2.不适合于实时读的场景。读的数据可能会是久的,因为步骤123需要花时间。CopyOnWriteArrayList只能做到最终一致性,无法满足实时性。它更适合读多、写少,的场景。 代码示例:
/**
* @author zjq
* @date 2021/08/08
* <p>title: CopyOnWriteArrayList</p>
* <p>description:</p>
*/
@Slf4j
@ThreadSafe
public class CopyOnWriteArrayListExample {
/**
* 请求总数
*/
public static int clientTotal = 5000;
/**
* 同时并发执行的线程数
*/
public static int threadTotal = 200;
public static List<Integer> arrayList = new CopyOnWriteArrayList<>();
public static void main(String[] args) throws Exception {
//创建线程池
ExecutorService executorService = Executors.newCachedThreadPool();
//信号量(并发线程数)
final Semaphore semaphore = new Semaphore(threadTotal);
//计数器 (把请求计数)
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
//信号量 判断进程是否执行
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
//计数器减1
countDownLatch.countDown();
});
}
//当所有请求结束
countDownLatch.await();
executorService.shutdown();
log.info("listSize:{}", arrayList.size());
}
private static void add() {
arrayList.add(1);
}
}
控制台输出:
22:00:59.094 [main] INFO com.zjq.concurrency.example.concurrent.CopyOnWriteArrayListExample - listSize:5000
说明是线程安全的
ConcurrentSkipListSet
TreeSet 对应ConcurrentSkipListSet,ConcurrentSkipListSet是jdk6新增的类,和TreeSet一样是支持自然排序的,和其他set一样,ConcurrentSkipListSet是基于map集合的,底层是ConcurrentSkipListMap实现的,在多线程环境下,ConcurrentSkipListSet里面的add,get,remove,都是线程安全的,多个线程可以安全的执行插入,移除,访问操作,但是对于批量操作,比如addAll, removeAll,ContainsAll(),并不能保证以原子方式执行,理由很简单,因为add本身是原子操作,但是当批量操作时,就不能保证原子性了,所以addAll, removeAll,ContainsAll(),批量操作时,需要自己手动加同步操作, 比如加锁,保证同一时间内,只能有一个线程操作,ConcurrentSkipListSet 不允许加入null。
本文内容到此结束了,
如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。
如有错误❌疑问💬欢迎各位大佬指出。
保持热爱,奔赴下一场山海。🏃🏃🏃
转载自:https://juejin.cn/post/7128692767793872932