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。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
1@Slf4j
2@ThreadSafe
3public class ConcurrentHashMapExample {
4    // 请求总数
5    public static int clientTotal = 5000;
6
7    // 同时并发执行的线程数
8    public static int threadTotal = 200;
9
10    private static Map<Integer, Integer> map = new ConcurrentHashMap<>();
11
12    public static void main(String[] args) throws InterruptedException {
13        //线程池
14        ExecutorService executorService = Executors.newCachedThreadPool();
15        //定义信号量
16        final Semaphore semaphore = new Semaphore(threadTotal);
17        //定义计数器
18        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
19        for(int i = 0; i < clientTotal; i++) {
20            final int count  = i;
21            executorService.execute(() ->{
22                try {
23                    semaphore.acquire();
24                    update(count);
25                    semaphore.release();
26                } catch (InterruptedException e) {
27
28                    log.error("exception", e);
29                }
30                countDownLatch.countDown();
31
32            });
33        }
34        countDownLatch.await();
35        executorService.shutdown();
36        log.info("size:{}",map.size()) ;
37    }
38
39    public static void update(int i) {
40        map.put(i,i);
41    }
42
43}
44

输出结果正确。

 concurrentSkipListMap:


1
2
1private static Map<Integer, Integer> map = new ConcurrentSkipListMap<>();
2

J.U.C

java高并发(十二)并发容器J.U.C

安全共享对象策略 – 总结

  • 线程限制:一个被线程限制的对象,由线程独占,并且只能被占有它的线程修改。
  • 共享只读:一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问,但是任何线程都不能修改它。
  • 线程安全对象:一个线程安全的对象或者容器,在内部通过同步机制来保证线程安全,所以其他线程无需额外的同步就可以通过公共接口随意访问它。
  • 被守护对象:被守护对象只能通过获取特定的锁来访问。

转载于:https://my.oschina.net/duanvincent/blog/3080769

给TA打赏
共{{data.count}}人
人已打赏
安全经验

如何避免Adsense违规封号

2021-10-11 16:36:11

安全经验

安全咨询服务

2022-1-12 14:11:49

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索