JUC学习之线程安全集合类
Posted 大忽悠爱忽悠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC学习之线程安全集合类相关的知识,希望对你有一定的参考价值。
JUC学习之线程安全集合类
线程安全集合类概述
线程安全集合类可以分为三大类:
- 遗留的线程安全集合如
Hashtable
—线程安全的map实现 ,Vector
—线程安全的list实现
性能低,不建议使用
- 使用
Collections
装饰的线程安全集合,如:
- Collections.synchronizedCollection
- Collections.synchronizedList
- Collections.synchronizedMap
- Collections.synchronizedSet
- Collections.synchronizedNavigableMap
- Collections.synchronizedNavigableSet
- Collections.synchronizedSortedMap
- Collections.synchronizedSortedSet
性能也不高,底层都是通过加同步锁实现的
- java.util.concurrent.*
重点介绍 java.util.concurrent.*
下的线程安全集合类,可以发现它们有规律,里面包含三类关键词:Blocking、CopyOnWrite、Concurrent
-
Blocking
大部分实现基于锁,并提供用来阻塞的方法—基于ReentrantLock实现 -
CopyOnWrite
之类容器修改开销相对较重 -
Concurrent
类型的容器 - 内部很多操作使用 cas 优化,一般可以提供较高吞吐量
- 弱一致性
- 遍历时弱一致性,例如,当利用迭代器遍历时,如果容器发生修改,迭代器仍然可以继续进行遍 历,这时内容是旧的
- 求大小弱一致性,size 操作未必是 100% 准确
- 读取弱一致性
遍历时如果发生了修改,对于非安全容器来讲,使用 fail-fast 机制也就是让遍历立刻失败,抛出ConcurrentModificationException,不再继续遍历
ConcurrentHashMap
练习:单词计数
生成测试数据
package com;
import lombok.extern.slf4j.Slf4j;
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 大忽悠
* @create 2022/1/8 15:20
*/
@Slf4j
public class Main
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);
//循环26次
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);
//循环26次
for (int i = 0; i < 26; i++)
//try...withResource写法
try (PrintWriter out = new PrintWriter(
new OutputStreamWriter(
new FileOutputStream("tmp/" + (i + 1) + ".txt"))))
String collect = list.subList(i * count, (i + 1) * count).stream()
//收集起来---通过\\n换行,完成集合中每个元素的拼接,最后返回一个字符串
.collect(Collectors.joining("\\n"));
//将上面拼接的字符串,输出到指定文件中
out.print(collect);
catch (IOException e)
模版代码,模版代码中封装了多线程读取文件的代码
package com;
import lombok.extern.slf4j.Slf4j;
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 大忽悠
* @create 2022/1/8 15:20
*/
@Slf4j
public class Main
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<>();
//准备26个线程
for (int i = 1; i <= 26; i++)
int idx = i;
Thread thread = new Thread(() ->
//从文件中读取字符放入list集合中,然后返回
List<String> words = readFromFile(idx);
//进行消费
consumer.accept(counterMap, words);
);
ts.add(thread);
//启动26个线程
ts.forEach(t -> t.start());
//等待26个线程都执行完毕
ts.forEach(t ->
try
t.join();
catch (InterruptedException e)
e.printStackTrace();
);
//打印map集合中的数据
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);
你要做的是实现两个参数
- 一是提供一个 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
下面的实现为:
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);
);
有没有问题?请改进
a=194, b=196, c=197, d=194, e=196, f=195, g=197, h=197, i=191, j=192, k=195, l=196, m=196, n=197, o=193, p=194, q=195, r=196, s=193, t=193, u=197, v=196, w=197, x=195, y=195, z=191
参考解答1
public static void main(String[] args)
demo(
() -> new ConcurrentHashMap<String, LongAdder>(),
(map, words) ->
for (String word : words)
// 注意不能使用 putIfAbsent,此方法返回的是上一次的 value,首次调用返回 null
map.computeIfAbsent(word, (key) -> new LongAdder()).increment();
);
输出
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
参考解答2
public static void main(String[] args)
demo(
() -> 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
ConcurrentHashMap 原理
1. JDK 7 HashMap 并发死链
JDK7 HashMap的实现是数组加链表的方式来实现的
并发死链的现象只有在JDK7的环境下才会复现
注意
- 要在 JDK 7 下运行,否则扩容机制和 hash 的计算方法都变了
- 以下测试代码是精心准备的,不要随便改动
package com;
import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
/**
* @author 大忽悠
* @create 2022/1/8 15:20
*/
@Slf4j
public class Main
public static void main(String[] args)
// 测试 java 7 中哪些数字的 hash 结果相等
System.out.println("长度为16时,桶下标为1的key");
for (int i = 0; i < 64; i++)
if (hash(i) % 16 == 1)
System.out.println(i);
System.out.println("长度为32时,桶下标为1的key");
for (int i = 0; i < 64; i++)
if (hash(i) % 32 == 1)
System.out.println(i);
// 1, 35, 16, 50 当大小为16时,它们在一个桶内
final HashMap<Integer, Integer> map = new HashMap<Integer, Integer>();
// 放 12 个元素
map.put(2, null);
map.put(3, null);
map.put(4, null);
map.put(5, null);
map.put(6, null);
map.put(7, null);
map.put(8, null);
map.put(9, null);
map以上是关于JUC学习之线程安全集合类的主要内容,如果未能解决你的问题,请参考以下文章