likes
comments
collection
share

JUC并发容器1(CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentSkipListSet)

作者站长头像
站长
· 阅读数 45

CopyOnWriteArrayList

ArrayList对应的线程安全的并发容器是CopyOnWriteArrayList HashSet对应的线程安全的并发容器是CopyOnWriteArraySet CopyOnWriteArraySet与CopyOnWriteArrayList类似,CopyOnWriteArraySet底层还是调用的CopyOnWriteArrayList的方法。

写操作的步骤

1.先拷贝一份原来的list 为 listNew 2.在新的数组listNew上做写操作 3.写完后将原来的数组list 指向新的数组listNew 这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器是一种读写分离的思想,读和写不同的容器、最终一致性以及使用另外开辟空间的思路,来解决并发冲突的思想。 CopyOnWriteArrayList的add操作都是在锁的保护下进行的,避免多线程并发做写操作时复制出多个副本。 JUC并发容器1(CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentSkipListSet)

读操作

是在原数组上读,不需要拷贝。

缺点

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。

本文内容到此结束了,

如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。

如有错误❌疑问💬欢迎各位大佬指出。

主页共饮一杯无的博客汇总👨‍💻

保持热爱,奔赴下一场山海。🏃🏃🏃

JUC并发容器1(CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentSkipListSet)

转载自:https://juejin.cn/post/7128692767793872932
评论
请登录