前言
set集合在java中本质上是无序的,关于为什么会无序,在底层源码中有很详细的说明。
本质上依旧还是一个 HashMap
集合,其中保存数据的方式为保存至key中。
关于更加详细的为什么是无序,可以参考另外一篇博客内容(java源码—hashmap源码分析(jdk1.8)),这里不做过多的阐述。
为什么多线程下的普通set集合不安全
如标题所述,为什么在多线程执行条件下,set集合存在不安全的问题,来看下列代码案例:
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class SetTest {
public static void main(String[] args) throws InterruptedException {
// 不安全
Set<String> sets = new HashSet();
for (int i = 1; i <= 40; i++) {
new Thread(()->{
sets.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(Thread.currentThread().getName()+"=="+sets);
},String.valueOf(i)).start();
}
}
}
在多线程操作同一个set集合对象时,增加数据依旧会出现 java.util.ConcurrentModificationException
异常信息。
Collections下的安全集合
和Collections集合工具类中,针对List创建安全集合的方式一样,也提供了一种创建安全set集合的方式。
import java.util.*;
public class SetTest {
public static void main(String[] args) throws InterruptedException {
// 不安全
//Set<String> sets = new HashSet();
Set<String> sets = Collections.synchronizedSet(new HashSet<>());
for (int i = 1; i <= 40; i++) {
new Thread(()->{
sets.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(Thread.currentThread().getName()+"=="+sets);
},String.valueOf(i)).start();
}
}
}
其中的源码和list集合类似:
也是在add操作执行的方法中添加了synchronized 同步代码块
,保证多个线程下,操作set集合的安全性。
JUC下的安全Set集合
同样在 jdk 1.5
后,juc针对set集合也提供有安全set集合类。如下所示:
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
public class SetTest {
public static void main(String[] args) throws InterruptedException {
// 不安全
//Set<String> sets = new HashSet();
//Set<String> sets = Collections.synchronizedSet(new HashSet<>());
Set<String> sets = new CopyOnWriteArraySet<>();
for (int i = 1; i <= 40; i++) {
new Thread(()->{
sets.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(Thread.currentThread().getName()+"=="+sets);
},String.valueOf(i)).start();
}
}
}
其源码逻辑如下所示:
和CopyOnWriteArrayList
中的方式一样,采取java.util.concurrent.locks.ReentrantLock
实现加锁和释放锁等操作。
1、调用add方法后,拿到java.util.concurrent.locks.ReentrantLock对象信息。
2、调用 lock.lock() 拿到锁!
3、将原数组对象copy操作,并创建原数组大小+1的新数组。
4、将新数据放入新数组中。
5、任何操作finally,都进行锁的释放