千家信息网

java中并发容器J.U.C怎么用

发表于:2024-11-29 作者:千家信息网编辑
千家信息网最后更新 2024年11月29日,这篇文章将为大家详细讲解有关java中并发容器J.U.C怎么用,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。并发容器是JDK提供的一个包名:java.
千家信息网最后更新 2024年11月29日java中并发容器J.U.C怎么用

这篇文章将为大家详细讲解有关java中并发容器J.U.C怎么用,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

并发容器是JDK提供的一个包名:java.util.concurrent

ArrayList -> CopyOnWriteArrayList

CopyOnWriteArrayList是线程安全的,写操作时复制,当有新元素添加到CopyOnWriteArrayList时先从原有的list中拷贝出来,然后在新的list上写操作,写完之后将原来的list指向新的list,整个操作都是在锁的保护下进行的,这样做为了防止多线程下多个add操作时产生多个副本,导致最终的数据不是我们期望的。

CopyOnWriteArrayList有几个缺点:

  1. 由于写操作时需要拷贝数组,因此比较消耗内存。当元素内容比较多时会导致Full GC

  2. 不能用于实时读的场景,拷贝数组需要时间,所以调用一个set操作后,读取到的数据还可能是旧的,虽然能做到最终一致性,但是无法满足实时性要求。因此CopyOnWriteArrayList更适合读多写少的场景。如果不清楚add或者set多少次操作,这个CopyOnWriteArrayList最好慎用。

HashSet、TreeSet->CopyOnWriteArraySet、ConcurrentSkipListSet

CopyOnWriteArraySet同样也是线程安全的,底层实现是CopyOnWriteArrayList,因此CopyOnWriteArraySet适合大小比较小的set集合只读操作大于写操作,因为需要复制基础数组,所以对于可变的操作(add set)的开销大。使用迭代器的迭代速度很快,而且不会有线程安全问题。

ConcurrentSkipListSet与TreeSet用一样,是支持自然排序的,可以在构造时自定义比较器。在多线程情况下ConcurrentSkipListSet里面的contains()、add()、remove()是线程安全的,多个线程可以并发的执行插入移除和访问操作,但是对于批量操作例如addAll(),removeAll(),retainAll()、containsAll()并不能保证以原子方式执行,这些操作可以被其他线程打断,需要额外增加锁才行,因为他们实现方式是分别调用contains()、add()、remove()的。因为并发容器只能保证每一次的contains()、add()、remove()操作时原子性的,而不能保证每一次批量操作都不会被其他线程打断。也就是多个add,多个remove操作时有其他线程进来。

HashMap、TreeMap -> ConcurrentHashMap、ConcurrentSkipListMap

ConcurrentHashMap不允许null,在实际的应用中除了少数的插入操作和删除操作外,绝大部分我们使用map都是使用读取操作,而且读操作大多数都是成功的,基于这个前提,ConcurrentHashMap针对读操作做了大量的优化,因此这个类具有很高的并发性,高并发场景下有很好的表现。

ConcurrentSkipListMap是TreeMap的线程安全版本。内部是使用skipList跳表的结构实现的。ConcurrentHashMap的存取速度是ConcurrentSkipListMap的4倍左右,但是ConcurrentSkipListMap的key是有序的而ConcurrentHashMap是做不到的,ConcurrentSkipListMap支持更高的并发,ConcurrentSkipListMap的存取时间是与线程数无关的,在数据量一定的情况下并发线程数越多ConcurrentSkipListMap越能体现出优势。

在较低并发情况下,可以使用Collections.synchronizedSortedMap()来实现,也可以提供较好的效率。在高并发的情况下可以使用ConcurrentSkipListMap提供更高的并发度。要对键值对进行排序时可以使用ConcurrentSkipListMap。

@Slf4j@ThreadSafepublic class ConcurrentHashMapExample {    // 请求总数    public static int clientTotal = 5000;    // 同时并发执行的线程数    public static int threadTotal = 200;    private static Map map = new ConcurrentHashMap<>();    public static void main(String[] args) throws InterruptedException {        //线程池        ExecutorService executorService = Executors.newCachedThreadPool();        //定义信号量        final Semaphore semaphore = new Semaphore(threadTotal);        //定义计数器        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);        for(int i = 0; i < clientTotal; i++) {            final int count  = i;            executorService.execute(() ->{                try {                    semaphore.acquire();                    update(count);                    semaphore.release();                } catch (InterruptedException e) {                    log.error("exception", e);                }                countDownLatch.countDown();            });        }        countDownLatch.await();        executorService.shutdown();        log.info("size:{}",map.size()) ;    }    public static void update(int i) {        map.put(i,i);    }}

输出结果正确。

concurrentSkipListMap:

private static Map map = new ConcurrentSkipListMap<>();

J.U.C

安全共享对象策略 - 总结

  • 线程限制:一个被线程限制的对象,由线程独占,并且只能被占有它的线程修改。

  • 共享只读:一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问,但是任何线程都不能修改它。

  • 线程安全对象:一个线程安全的对象或者容器,在内部通过同步机制来保证线程安全,所以其他线程无需额外的同步就可以通过公共接口随意访问它。

  • 被守护对象:被守护对象只能通过获取特定的锁来访问。

关于java中并发容器J.U.C怎么用就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

0