• JUC P7 线程安全集合 基础+代码


    JUC P7 线程安全集合 基础+代码

    教程:https://www.bilibili.com/video/BV16J411h7Rd

    13. 线程安全集合类

    13.1 概述

    13.1.1 老古董

    HashTableVector

    13.1.2 使用 Collections 修饰的线程安全类(装饰器模式的思想)

    在这里插入图片描述

    13.1.3 JUC 安全集合类
    13.1.3.1 CopyOnWrite

    修改开销相对较重,基于 synchronized,适用于读多写少的场景
    在这里插入图片描述

    13.1.3.2 Blocking

    大部分实现基于锁(ReentrantLock),并提供用来阻塞的方法
    在这里插入图片描述

    13.1.3.3 Concurrent

    内部很多操作使用 CAS 优化,一般可以提供较高的吞吐量
    在这里插入图片描述

    • 弱一致性
      • 遍历时弱一致性,例如,当利用迭代器遍历时,若容器发生修改,迭代器仍然可以继续进行遍历,这时内容是旧的
      • 求容量大小弱一致性,size() 操作未必 100% 准确
      • 读取弱一致性

    Note:
    对于非安全容器来讲,遍历过程中发生修改,使用了 fail-fast 的机制会让遍历立刻失败,抛出 ConcurrentModificationException,不再继续遍历。

    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);
    list.add(5);
    new Thread(() -> {
        list.forEach(System.out::println);
    }).start();
    new Thread(() -> list.add(6)).start();
    new Thread(() -> list.add(7)).start();
    new Thread(() -> list.add(8)).start();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    13.2 ConcurrentHashMap

    13.2.1 练习:单词计数

    随机生成 26 个字母,每个字母生成 200 次,打乱后装入 26 个文件中。

    static final String ALPHA = "abcedfghijklmnopqrstuvwxyz";
    
    public static void main(String[] args) {
        int length = ALPHA.length();
        int count = 200;
        List<String> list = new ArrayList<>(length * count);
        for (int i = 0; i < length; i++) {
            char ch = ALPHA.charAt(i);
            for (int j = 0; j < count; j++) {
                list.add(String.valueOf(ch));
            }
        }
        Collections.shuffle(list);
        for (int i = 0; i < 26; i++) {
            try (PrintWriter out = new PrintWriter(
                    new OutputStreamWriter(
                            new FileOutputStream("G://tmp/" + (i + 1) + ".txt")))) {
                String collect = String.join("\n", list.subList(i * count, (i + 1) * count));
                out.print(collect);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这里插入图片描述

    使用 HashMap 统计,可以看到结果不正确,正确应该是每个字母出现次数都是 200:

    public static void main(String[] args) {
        demo(
            // 创建 map 集合
            // 创建 ConcurrentHashMap 对不对?
            () -> new HashMap<String, Integer>(),
            // 进行计数
            (map, words) -> {
                for (String word : words) {
                    Integer counter = map.get(word);
                    int newValue = counter == null ? 1 : counter + 1;
                    map.put(word, newValue);
                }
            }
        );
    }
    
    private static <V> void demo(Supplier<Map<String, V>> supplier,
                                 BiConsumer<Map<String, V>, List<String>> consumer) {
        Map<String, V> counterMap = supplier.get();
        List<Thread> ts = new ArrayList<>();
        for (int i = 1; i <= 26; i++) {
            int idx = i;
            Thread thread = new Thread(() -> {
                List<String> words = readFromFile(idx);
                consumer.accept(counterMap, words);
            });
            ts.add(thread);
        }
        ts.forEach(Thread::start);
        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println(counterMap);
    }
    
    public static List<String> readFromFile(int i) {
        ArrayList<String> words = new ArrayList<>();
        try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream("G://tmp/"
                + i + ".txt")))) {
            while (true) {
                String word = in.readLine();
                if (word == null) {
                    break;
                }
                words.add(word);
            }
            return words;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    • 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
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    在这里插入图片描述

    ConcurrentHashMap 错误用法

    假如把 HashMap 换成 ConcurrentHashMap 正确吗?

    () -> new ConcurrentHashMap<String, Integer>()
    
    • 1

    在这里插入图片描述
    可以看到结果也不正确!什么情况?

    • 因为只能保证一个方法是原子的,当多个方法交错使用,那么这个整体并不保证是原子的
      在这里插入图片描述
    解决方案 1 (上 synchronized 锁)
    () -> new ConcurrentHashMap<String, Integer>(),
    // 进行计数
    (map, words) -> {
        for (String word : words) {
            synchronized (map) {
                Integer counter = map.get(word);
                int newValue = counter == null ? 1 : counter + 1;
                map.put(word, newValue);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这样确实能解决问题,但是并发度不高。

    解决方案 2(putIfAbsent 和 computeIfPresent)
    () -> new ConcurrentHashMap<String, Integer>(),
    // 进行计数
     (map, words) -> {
         for (String word : words) {
             map.putIfAbsent(word, 0);
             map.computeIfPresent(word, (key, value) -> value + 1);
         }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    解决方案 3 (LongAdder 累加器)
    () -> new ConcurrentHashMap<String, LongAdder>(),
    // 进行计数
    (map, words) -> {
        for (String word : words) {
            LongAdder value = map.computeIfAbsent(word, (key) -> new LongAdder());
            value.increment();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    对比

    经过大量测试,大部分情况都是第三种方案更胜一筹,第一种方案次之,最差的就是第二种方案:

    在这里插入图片描述

    12.2.2 JDK 7 HashMap 并发死链

    比较形象的例子:jdk1.7中 hashmap为什么会发生死链? - 磊哥的回答 - 知乎

    12.2.3 JDK 8 HashMap 并发数据不一致

    两个线程同时插入尾节点,出现顶替的情况,造成数据丢失。以及两个线程同时修改数据的情况。

    12.2.4 为什么 ConcurrentHashMap 不允许插入的 key/value 为 null ?

    第一,代码中不允许!
    在这里插入图片描述

    第二,这会带来并发场景下的歧义:

    举个例子:

    • 单线程下,比如使用 map.get(key) 方法后的结果返回 null,我们要判断 ① key 对应的值是 null,② 还是 map 中没有这个 key,有这两种情况。这时候我们可以使用 map.containsKey(key) 方法看返回的是 true 还是 false。如果是 true,代表 key 对应的值为 null,如果是 false,说明 map 中没有这个 key。单线程情况下确实可以使用额外的方法判定消除歧义。
    • 多线程下,判断是否含有 key 和获取 key 对应的值两个方法因为是分开的操作,这样无法保证一个线程下执行两个方法过程中是否有另外一个线程进行捣乱。假如说初始状态下 mapkey 对应 "hello"
    时刻线程1线程2
    1map.containsKey(key) 返回 true
    2map.put(key, null)
    3map.get(key),期待:"hello",实际:null

    13.3 CopyOnWriteArrayList

    底层实现采用了写入时拷贝的思想,增删改操作会将底层数组拷贝一份,更改操作在新数组上执行,这时不影响其他线程的并发读读写分离

    读-读,读-写都可以并发执行,写-写是互斥的。

    适用于读多写少的场景。写过程太多会一直创建新的数组,典型的以空间换时间。

    弱一致性
    @SneakyThrows
    public static void main(String[] args) {
        CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        Iterator<Integer> iter = list.iterator();
        new Thread(() -> {
            list.remove(0);
            System.out.println(list);
        }).start();
        
        TimeUnit.SECONDS.sleep(1);
        
        while (iter.hasNext()) {
            System.out.println(iter.next());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述
    弱一致性:

    • MySQL 的 MVCC
    • 高并发和一致性是矛盾的,需要权衡
    • 将读写分离
  • 相关阅读:
    2023数字科技生态展,移远通信解锁新成就
    Quarto Dashboards 教程 2:Dashboard Layout
    SpringBoot大文件上传实现分片、断点续传
    支持Python的新版vTESTstudio测试用例编写方法大集合(上)
    关于ETL的两种架构(ETL架构和ELT架构)
    安全评估报告怎么写 安全风险评估报告流程 安全评估报告认定
    react 生命周期
    axios和Ajax
    电子画册如何制作,教你几分钟简单上手制作?
    私有化部署AI智能客服,解放企业成本,提升服务效率
  • 原文地址:https://blog.csdn.net/qq_39906884/article/details/127697122