多线程之线程安全集合类

197 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第30天,点击查看活动详情

1 概述

image.png

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

  • 遗留的线程安全集合如 Hashtable , Vector

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

    • Collections.synchronizedCollection
    • Collections.synchronizedList
    • Collections.synchronizedMap
    • Collections.synchronizedSet
    • Collections.synchronizedNavigableMap
    • Collections.synchronizedNavigableSet
    • Collections.synchronizedSortedMap
    • Collections.synchronizedSortedSet
  • java.util.concurrent.*包下面类

2 JUC安全集合

JUC安全集合中主要包括Blocking、CopyOnWrite、Concurrent.

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

注:

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

3 ConcurrentHashMap应用

案例: 统计单词数

测试数据

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

执行代码

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(t->t.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("tmp/"
    + i +".txt")))) {
         while(true) {
             String word = in.readLine();
             if(word == null) {
                 break;
             }
             words.add(word);
         }
         return words;
     } catch (IOException e) {
         throw new RuntimeException(e);
     }
}

目标结果:

{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} 

实现代码

demo(
     // 创建 map 集合
     () -> 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);
         }
     }
);

执行结果,没有得到预期效果, 因为循环中组合不是线程安全操作.

优化方案1

demo(
 () -> new ConcurrentHashMap<String, LongAdder>(),
 (map, words) -> {
     for (String word : words) {
         // 注意不能使用 putIfAbsent,此方法返回的是上一次的 value,首次调用返回 null
         map.computeIfAbsent(word, (key) -> new LongAdder()).increment();
     }
 }
);

优化方案2

demo(
 () -> new ConcurrentHashMap<String, Integer>(),
 (map, words) -> {
     for (String word : words) {
         // 函数式编程,无需原子变量
         map.merge(word, 1, Integer::sum);
     }
 }
);