ConcurrentHashMap简单案例

concurrenthashmap

线程安全集合类

image-20220926095855055

线程安全基本分类

线程安全集合类可以分为三大类:

  • 遗留的线程安全集合如 HashtableVector

  • 使用 Collections 装饰的线程安全集合,如

    • Collections.synchronizedCollection
    • Collections.synchronizedList
    • Collections.synchronizedMap
    • Collections.synchronizedSet
    • Collections.synchronizedNavigableMap
    • Collections.synchronizedNavigableSet
    • Collections.synchronizedSortedMap
    • Collections.synchronizedSortedSet
  • java.util.concurrent.*

重点介绍 java.util.concurrent.* 下的线程安全集合类,可以发现它们有规律,里面包含三类关键词:BlockingCopyOnWriteConcurrent

  • Blocking 大部分实现基于锁,并提供用来阻塞的方法
  • CopyOnWrite 之类容器修改开销相对较重
  • Concurrent 类型的容器 :
    • 内部很多操作使用 cas 优化,一般可以提供较高吞吐量
    • 弱一致性 : 遍历时弱一致性,例如,当利用迭代器遍历时,如果容器发生修改,迭代器仍然可以继续进行遍历,这时内容是旧的
    • 求大小弱一致性,size 操作未必是 100% 准确
    • 读取弱一致性

遍历时如果发生了修改,对于非安全容器来讲,使用 fail-fast 机制也就是让遍历立刻失败,抛出 ConcurrentModifificationException,不再继续遍历

ConcurrentHashMap

计数案例

案例要求

统计26个文件中单词的个数 :

  • 一是提供一个 map 集合,用来存放每个单词的计数结果,key 为单词,value 为计数

  • 二是提供一组操作,保证计数的安全性,会传递 map 集合以及 单词 List

正确结果输出应该是每个单词出现 200 次 :

{a=200, b=200, c=200, d=200, e=200, f=200, g=200, h=200, i=200, j=200, k=200, l=200, m=200, 
n=200, o=200, p=200, q=200, r=200, s=200, t=200, u=200, v=200, w=200, x=200, y=200, z=200}

文件生成 :

package cn.knightzz.concurrent.examples;

import javax.xml.crypto.Data;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author 王天赐
 * @title: DataGeneration
 * @projectName hm-juc-codes
 * @description: 数据生成
 * @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
 * @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
 * @create: 2022-09-26 10:10
 */
@SuppressWarnings("all")
public class DataGeneration {

    static final String ALPHA = "abcedfghijklmnopqrstuvwxyz";
    static final String RESOURCE_PATH = "juc-chapter-08/src/main/resources/tmp/";

    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(RESOURCE_PATH + (i + 1) + ".txt")))) {
                String collect = list.subList(i * count, (i + 1) * count).stream()
                        .collect(Collectors.joining("\n"));
                out.print(collect);
            } catch (IOException e) {
            }
        }
    }
}

代码实现

多线程读取的代码 :

package cn.knightzz.concurrent.examples;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Supplier;

/**
 * @author 王天赐
 * @title: FileUtils
 * @projectName hm-juc-codes
 * @description:
 * @website <a href="http://knightzz.cn/">http://knightzz.cn/</a>
 * @github <a href="https://github.com/knightzz1998">https://github.com/knightzz1998</a>
 * @create: 2022-09-26 10:36
 */
@SuppressWarnings("all")
public class FileUtils {

    static final String RESOURCE_PATH = "juc-chapter-08/src/main/resources/tmp/";

    public static List<String> readFromFile(int i) {
        ArrayList<String> words = new ArrayList<>();
        try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(RESOURCE_PATH + i + ".txt")))) {
            while (true) {
                String word = in.readLine();
                if (word == null) {
                    break;
                }
                words.add(word);
            }
            return words;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }



}

  /**
     * 多线程读取
     *
     * @param supplier 提供存储结果的map集合
     * @param consumer 用于对数据进行处理
     * @param <V>
     */
    public static <V> void multithreadedRead(Supplier<Map<String, V>> supplier, BiConsumer<Map<String, V>, List<String>> consumer) {
        Map<String, V> counterMap = supplier.get();
        // key value
        // a   200
        // b   200
        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 去处理数据
                consumer.accept(counterMap, words);
            });
            ts.add(thread);
        }

        ts.forEach(t -> t.start());
        ts.forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        System.out.println(counterMap);
    }

基本思路是:

  • 检查 map中是否存在 key
    • 如果有 : 获取 key -> value , 然后+1
    • 如果没有 : 把 key 存入map

主要可能存在并发问题的地方 : map 是共享的, 在判断的时候会出现并发问题 . map 仅能保证某个方法内线程安全, 即线程不会出现上下文切换

	 multithreadedRead(
                () -> new ConcurrentHashMap<String, Integer>(),
                (map, words) -> {
                    for (String word : words) {
                        Integer value = map.get(word);
                        if (value != null) {
                            map.put(word, value+1);
                        }else {
                            map.put(word, 0);
                        }
                    }
                }
        );

就比如上面的部分, 虽然 ConcurrentHashMap 里面的方法是线程安全的, 但是多个线程安全的方法放到一起就不是线程安全的

map.put(word, value+1); 这个方法仅仅能保证方法内部的线程安全, 假设出现下面的情况 :

image-20220926143251731

线程安全的实现 :

 public static void main(String[] args) {
        multithreadedRead(
                () -> new ConcurrentHashMap<String, LongAdder>(),
                (map, words) -> {
                    for (String word : words) {
                        LongAdder longAdder = map.computeIfAbsent(word, (key) -> new LongAdder());
                        longAdder.increment();
                    }
                }
        );
    }
multithreadedRead(
                () -> new ConcurrentHashMap<String, Integer>(),
                (map, words) -> {
                    for (String word : words) {
                        map.merge(word, 1, Integer::sum);
                    }
                }
        );

执行结果如下 :

{a=200, b=200, c=200, d=200, e=200, f=200, g=200, h=200, i=200, j=200, k=200, l=200, m=200, n=200, o=200, p=200, q=200, r=200, s=200, t=200, u=200, v=200, w=200, x=200, y=200, z=200}

Process finished with exit code 0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

兀坐晴窗独饮茶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值