Day9 多线程进阶 3 集合类不安全
uwupu 啦啦啦啦啦

引入:集合类不安全

例:使用多线程操作List<String>

代码

1
2
3
4
5
6
7
8
9
10
11
12
public class Demo9_多线程操作字符串ArrayList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();

for (int i = 0; i < 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));//随机生成一个字符串,添加到list中。
System.out.println(list);
},String.valueOf(i)).start();//线程名为i对应的值
}
}
}

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[058ac]
[058ac, e749b, a5fec]
[058ac, e749b, a5fec, a8e9a]
[058ac, e749b, a5fec, a8e9a, 06bae, 09588]
[058ac, e749b, a5fec, a8e9a, 06bae]
[058ac, e749b, a5fec, a8e9a, 06bae, 09588, 20884]
[058ac, e749b, a5fec, a8e9a, 06bae, 09588, 20884, 1edab]
[058ac, e749b, a5fec, a8e9a, 06bae, 09588, 20884, 1edab, ce3d3]
[058ac, e749b, a5fec, a8e9a, 06bae, 09588, 20884, 1edab, ce3d3, 86bcf]
Exception in thread "2" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at com.yn.Demo9_多线程操作字符串ArrayList.lambda$main$0(Demo9_多线程操作字符串ArrayList.java:14)
at java.lang.Thread.run(Thread.java:748)

Process finished with exit code 0

java.util.ConcurrentModificationException

上面例子中出现了java.util.ConcurrentModificationException异常。

java.util.ConcurrentModificationException异常,即:并发修改异常

结论:并发下ArrayList 不安全

解决方案

  1. List<String> list = new Vector<>();

    Vector是线程安全的,ArrayList是线程不安全的。

    Vector类下,add()方法带有synchronized修饰;ArrayList下的add()方法没有synchronized修饰。

  2. List<String> list = Collections.synchronizedList(new ArrayList<>());

  3. ( JUC方案 )List<String> list = new CopyOnWriteArrayList<>();

CopyOnWriteArrayList

写入时复制

原理

首先把原数组复制一份,然后把要添加的内容添加进新数组,最后将新的数组直接赋值给原引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);//复制数组
newElements[len] = e;//插入
setArray(newElements);//新的数组赋值给原引用
return true;
} finally {
lock.unlock();
}
}
  • 写入时复制
  • 读写分离

CopyOnWriteArrayListVector

CopyOnWriteArrayList效率比Vector高。

Set不安全

image

例:使用多线程操作Set<String>

代码

1
2
3
4
5
6
7
8
9
10
11
public class Demo10_多线程操作字符串Set {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
for (int i = 0; i < 15; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[1cb01, eb758, aa01b, 9af06, c57bf, 847f3, 2b7e6, e2f07, 96c2f, bc219, b0027]
[1cb01, eb758, aa01b, 9af06, c57bf, 847f3, 2b7e6, e2f07, 96c2f, bc219, b0027]
[1cb01, eb758, aa01b, 9af06, c57bf, 847f3, 2b7e6, e2f07, 96c2f, bc219, b0027]
[1cb01, eb758, aa01b, 9af06, c57bf, 847f3, 2b7e6, e2f07, 96c2f, bc219, b0027]
[eb758, aa01b, c57bf, 847f3, e2f07, 96c2f, b9460, bc219, 1cb01, 9af06, 2b7e6, b0027, 93907]
[eb758, aa01b, c57bf, 847f3, e2f07, 96c2f, b9460, bc219, 1cb01, 9af06, 2b7e6, b0027, 93907]
[1cb01, eb758, aa01b, 9af06, c57bf, 847f3, 2b7e6, e2f07, 96c2f, b9460, bc219, b0027]
[1cb01, eb758, aa01b, 9af06, c57bf, 847f3, 2b7e6, e2f07, 96c2f, b9460, bc219, b0027]
[eb758, aa01b, 6e750, c57bf, 847f3, e2f07, 96c2f, b9460, bc219, 1cb01, 9af06, 2b7e6, b0027, 93907]
[eb758, aa01b, 6e750, c57bf, 847f3, e2f07, 96c2f, b9460, bc219, 1cb01, 9af06, 2b7e6, b0027, 93907]
[eb758, aa01b, 6e750, c57bf, 847f3, e2f07, 96c2f, b9460, bc219, 1cb01, 9af06, 2b7e6, b0027, 93907]
[eb758, aa01b, 6e750, c57bf, 847f3, e2f07, 96c2f, b9460, bc219, 1cb01, 9af06, 2b7e6, b0027, 93907]
[eb758, aa01b, 6e750, c57bf, 847f3, e2f07, 96c2f, b9460, bc219, 1cb01, 9af06, 2b7e6, b0027, 93907]
[eb758, aa01b, 6e750, c57bf, 847f3, e2f07, 96c2f, b9460, bc219, 1cb01, 9af06, 2b7e6, b0027, 93907]
Exception in thread "1" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1445)
at java.util.HashMap$KeyIterator.next(HashMap.java:1469)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at com.yn.Demo10_多线程操作字符串Set.lambda$main$0(Demo10_多线程操作字符串Set.java:13)
at java.lang.Thread.run(Thread.java:748)

出现了错误java.util.ConcurrentModificationException

解决方案

  1. Set<String> set = Collections.synchronizedSet(new HashSet<>());
  2. ( JUC方案 ) Set<String> set = new CopyOnWriteArraySet<>();

HashSet底层

1
2
3
4
5
6
7
8
9
10
public HashSet() {
map = new HashMap<>();
}

//add set的本质是map的key key是无法宠物的
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}

private static final Object PRESENT = new Object();//PRESENT是一个常量,是一个不变的值。

HashSet的底层是HashMap

多线程的HashMap

引入:

Map<String,String> map = new HashMap<>();

map是这么用的吗?默认等价于什么?

不是这样用的。用法:Hole….(挖坑,后续填)

默认等价于new HashMap<>(16,0.75)

HashMap

HashMap是一个集合,键值对的集合,每个节点用Node<K,V>表示

Node

1
2
3
4
5
6
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}

Node是一个内部类,key为键,value为值,next指向下一个元素。

数据结构

HashMap的数据结构为:数组+(链表或红黑树)

  • 在JDK1.8之前,HashMap的数据结构为“数组 + 链表”,数组是HashMap的主体,链表则是为了解决哈希冲突问题;
  • Jdk1.8之后,当链表长度大于阈值(或者红黑树的边界值,默认为8),并且当前数组长度大于64时,此索引位置上的所有数据改为使用红黑树存储。

注:若链表长度大于64,但数组长度小于64,此时并不会将链表转换为红黑树,而是进行数组扩容。

因为红黑树需要保持平衡,影响效率,为了提高性能和减少搜索时间,当链表长度大于阈值且数组长度大于64时,链表才会转换为红黑树。

HashMap的构造函数

1
2
3
4
5
6
7
8
9
10
11
12
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}

HashMap有两个参数:initialCapacity 和 loadFactor (初始容量和加载因子)

  • 初始容量是创建时数组分配的容量大小,默认为16;
  • 加载因子
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
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {// K 键 V值 onlyIfAbsent evict
Node<K,V>[] tab; Node<K,V> p; int n, i;//tab 为节点头
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;



if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}


++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

例:使用多线程操作Set<String>

代码

1
2
3
4
5
6
7
8
9
10
11
public class Demo11_多线程操作HashMap {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}

输出

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
{0=c16d6, 1=a939d, 3=96525, 4=27c75}
{0=c16d6, 1=a939d, 3=96525, 4=27c75, 5=ead0a, 6=a81ee, 7=96a76, 9=ec2fb}
{0=c16d6, 1=a939d, 3=96525, 4=27c75, 5=ead0a, 6=a81ee, 9=ec2fb}
{0=c16d6, 1=a939d, 3=96525, 4=27c75, 5=ead0a, 9=ec2fb}
{0=c16d6, 1=a939d, 3=96525, 4=27c75, 5=ead0a}
{0=c16d6, 1=a939d, 3=96525, 4=27c75}
{0=c16d6, 1=a939d, 3=96525, 4=27c75, 5=ead0a, 6=a81ee, 7=96a76, 8=78b79, 9=ec2fb}
Exception in thread "3" Exception in thread "2" Exception in thread "0" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1445)
at java.util.HashMap$EntryIterator.next(HashMap.java:1479)
at java.util.HashMap$EntryIterator.next(HashMap.java:1477)
at java.util.AbstractMap.toString(AbstractMap.java:554)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at com.yn.Demo11_多线程操作HashMap.lambda$main$0(Demo11_多线程操作HashMap.java:13)
at java.lang.Thread.run(Thread.java:748)
java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1445)
at java.util.HashMap$EntryIterator.next(HashMap.java:1479)
at java.util.HashMap$EntryIterator.next(HashMap.java:1477)
at java.util.AbstractMap.toString(AbstractMap.java:554)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at com.yn.Demo11_多线程操作HashMap.lambda$main$0(Demo11_多线程操作HashMap.java:13)
at java.lang.Thread.run(Thread.java:748)
java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1445)
at java.util.HashMap$EntryIterator.next(HashMap.java:1479)
at java.util.HashMap$EntryIterator.next(HashMap.java:1477)
at java.util.AbstractMap.toString(AbstractMap.java:554)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at com.yn.Demo11_多线程操作HashMap.lambda$main$0(Demo11_多线程操作HashMap.java:13)
at java.lang.Thread.run(Thread.java:748)

解决方案

  1. Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
  2. ( JUC方案 ) Map<String,String> map = new ConcurrentHashMap<>();

image

ConcurrentHashMap 介绍….-> Hole…

其他

随机生成字符串

UUID.randomUUID().toString()

可以随机生成字符串,使用substring(0,x)取出其中一部分。

 评论