32.并发容器(Map、List、Set)实战及其原理

JUC包下的并发容器

java的集合容器框架主要有四大类别:List、Set、Queue、Map,都是非线程安全的。

java先提供了同步容器供用户使用,可以简单地理解为通过synchronized来实现同步的容器,比如Vector、Hashtable以及SynchronizedList。

java.util.concurrent包中提供了多种并发类容器,解决同步容器的性能问题。

  • Map
    • ConcurrentHashMap 代替Hashtable、synchronizedMap,支持复合操作
      • JDK6中采用一种更加细粒度的加锁机制Segment“分段锁”,JDK8中采用CAS无锁算法。
      • 应用场景
        • 共享数据的线程安全:在多线程编程中,如果需要进行共享数据的读写,可以使用 ConcurrentHashMap 保证线程安全。
        • 缓存:ConcurrentHashMap 的高并发性能和线程安全能力,使其成为一种很好的缓存实现方案。在多线程环境下,使用 ConcurrentHashMap 作为缓存的数据结构,能够提高程序的并发性能,同时保证数据的一致性。
    • ConcurrentSkipListMap 代替synchronizedSortedMap(TreeMap)
      • Skip list(跳表)是一种可以代替平衡树的数据结构,默认是按照Key值升序的。
  • List
    • CopyOnWriteArrayList 代替Vector、synchronizedList
      • 利用高并发往往是读多写少的特性,对读操作不加锁,对写操作,先复制一份新的集合,在新的集合上面修改,然后将新集合赋值给旧的引用,并通过volatile 保证其可见性,当然写操作的锁是必不可少的了。
      • 应用场景
        • 读多写少,因为读不需要加锁
        • 不需要实时更新数据,因为读取的可能不是最新的
      • 缺陷
        • 由于写操作的时候会拷贝数组,会消耗额外内存
        • 因为拷贝数组,新增元素都需要时间,不能用于实时读的场景
        • 因为add/set都需要复制数组,注意数组大小,容易引起故障
      • fail-fast 机制是java集合(Collection)中的一种错误机制。抛出 ConcurrentModificationException
        • 方案一:在遍历过程中所有涉及到改变modCount 值的地方全部加上synchronized 或者直接使用 Collection#synchronizedList
        • 方案二:使用CopyOnWriteArrayList 替换 ArrayList,推荐使用该方案(即fail-safe)。
  • Set
    • CopyOnWriteArraySet 代替synchronizedSet
      • 基于CopyOnWriteArrayList实现,其唯一的不同是在add时调用的是CopyOnWriteArrayList的addIfAbsent方法,其遍历当前Object数组,如Object数组中已有了当前元素,则直接返回,如果没有则放入Object数组的尾部,并返回。
    • ConcurrentSkipListSet
  • Queue
    • BlockingQueue
    • ConcurrentLinkedQueue
    • BlockingDeque
    • ConcurrentLinkedDeque
import sun.misc.Unsafe;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;

public class ConcurrentDemo {

    private static final ConcurrentDemo d = new ConcurrentDemo();

    //    private static ArrayList<String> arr = new ArrayList<>();
    private static CopyOnWriteArrayList<String> arr = new CopyOnWriteArrayList<>();

    public static void main(String[] args) {
        //cas案例
//        casDemo();
        //统计文件中英文字母出现的总次数
        mapDemo();

        arr.add("h");
        arr.add("e");
        arr.add("l");
        arr.add("l");
        arr.add("o");

        var t1 = new Thread(new AddElement());
        var t2 = new Thread(new AddElement());
        t1.start();
        t2.start();
        System.out.println(arr.size());
        //java.util.ConcurrentModificationException
        for (String s : arr) {
            System.out.print(s);
        }

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }


    }

    static class AddElement implements Runnable {

        @Override
        public void run() {
            var start = 0;//97
            for (int i = 0; i < start + 1; i++) {
//                try {
//                    Thread.sleep(5);
//                } catch (InterruptedException e) {
//                    throw new RuntimeException(e);
//                }
//                arr.add(Character.toString(i));
                arr.add(Integer.toString(i));
            }
        }
    }

    static void casDemo() {
        var d2 = d.new HashMapTest();
        for (int i = 50; i < 100; i++) {
            int num = i;
            new Thread(() -> {
                try {
                    if (d2.demo(1, num))
                        System.out.println(num + " updated success");
                } catch (NoSuchFieldException | NoSuchMethodException | InvocationTargetException |
                         InstantiationException | IllegalAccessException e) {
                    throw new RuntimeException(e);
                }
            }).start();
        }
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println(d2);
    }

    static void mapDemo() {
        var d2 = d.new ConcurrentHasMapDemo();
//        d2.test();
        d2.demo();
    }

    class ConcurrentHasMapDemo {

        private static ConcurrentHashMap<String, AtomicLong> counter = new ConcurrentHashMap<>();
        private static CountDownLatch countDownLatch = new CountDownLatch(3);

        private static String[] words = {"you", "and", "me"};

        public void demo() {
            var task = new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 10; i++) {
                        // 模拟从文本文件中读取到的单词
                        String word = words[new Random().nextInt(words.length)];
                        // 尝试获取全局统计结果
                        AtomicLong number = counter.get(word);
                        // 在未获取到的情况下,进行初次统计结果设置
                        if (number == null) {
                            // 在设置时发现如果不存在则初始化
                            AtomicLong newNumber = new AtomicLong(0);
                            number = counter.putIfAbsent(word, newNumber);
                            if (number == null) {
                                number = newNumber;
                            }
                        }
                        // 在获取到的情况下,统计次数直接加1
                        number.incrementAndGet();

                        System.out.println(Thread.currentThread().getName() + ":" + word + " 出现第 " + number + " 次");
                    }
                    countDownLatch.countDown();
                }
            };

            new Thread(task, "线程1").start();
            new Thread(task, "线程2").start();
            new Thread(task, "线程3").start();

            try {
                countDownLatch.await();
                System.out.println(counter.toString());
            } catch (Exception e) {
            }
        }

        public void test() {
            var map = new ConcurrentHashMap<>();
            map.put("key", "value");
            map.put("hello", "world");
            System.out.println(map.size());
            System.out.println(map);
            //如果 key 对应的 value 不存在,则 put 进去,返回 null。否则不 put,返回已存在的 value。
            Object o = map.putIfAbsent("hello", "world2");
            System.out.println(o.toString());
            //如果 key 对应的值是 value,则移除 K-V,返回 true。否则不移除,返回 false。
            boolean remove = map.remove("hello", "world2");
            System.out.println("remove:" + remove);
            //如果 key 对应的当前值是 oldValue,则替换为 newValue,返回 true。否则不替换,返回 false。
            Object replace = map.replace("hello", "world", "world2");
            System.out.println("replace:" + replace);
            System.out.println(map);
        }
    }

    class HashMapTest {

        private int value = 1;

        public boolean demo(int expectedValue, int newValue) throws NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
            Constructor<Unsafe> unsafe = Unsafe.class.getDeclaredConstructor();
            unsafe.setAccessible(true);
            Unsafe u = unsafe.newInstance();
            var offset = u.objectFieldOffset(HashMapTest.class.getDeclaredField("value"));
            return u.compareAndSwapInt(this, offset, expectedValue, newValue);
        }

        @Override
        public String toString() {
            return "HashMapTest{" +
                    "value=" + value +
                    '}';
        }
    }
}

跳表

跳表是一种基于有序链表的数据结构,支持快速插入、删除、查找操作,其时间复杂度为O(log n),比普通链表的O(n)更高效。

https://cmps-people.ok.ubc.ca/ylucet/DS/SkipList.html

跳表的特性有这么几点:

  • 一个跳表结构由很多层数据结构组成。
  • 每一层都是一个有序的链表,默认是升序。也可以自定义排序方法。
  • 最底层链表包含了所有的元素。
  • 如果每一个元素出现在LevelN的链表中(N>1),那么这个元素必定在下层链表出现。
  • 每一个节点都包含了两个指针,一个指向同一级链表中的下一个元素,一个指向下一层级别链表中的相同值元素。

电商场景中并发容器的选择

  • 案例一:电商网站中记录一次活动下各个商品售卖的数量。
    • 选型:Hashtable 不推荐,锁太重,选ConcurrentHashMap 确保高并发下多线程的安全性
  • 案例二:在一次活动下,为每个用户记录浏览商品的历史和次数。
    • 如果对数据有强一致要求,则需使用Hashtable;在大部分场景通常都是弱一致性的情况下,使用ConcurrentHashMap 即可;如果数据量级很高,且存在大量增删改操作,则可以考虑使用ConcurrentSkipListMap。
  • 案例三:在活动中,创建一个用户列表,记录冻结的用户。一旦冻结,不允许再下单抢购,但是可以浏览。
    • 综合业务场景,选CopyOnWriteArrayList,会占空间,但是也仅仅发生在添加新冻结用户的时候。绝大多数的访问在非冻结用户的读取和比对上,不会阻塞。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值