大厂面试题:我们知道Set是线程不安全的,请编码写一个不安全的案例并给出解决方案?
1、Set线程不安全问题产生
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class ContainerNotSafeDemoTwo {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString());
System.out.println(set.toString());
}, "T1").start();
}
}
}
程序执行结果如下:报java.util.ConcurrentModificationException异常
2、ConcurrentModificationException产生的原因
一个线程正在写,另一个线程过来抢占资源,会造成数据不一致,进而报并发修改异常。
3、Set线程不安全解决方案
(1)第一种解决方案
使用Collections工具类创建同步集合类
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
public class ContainerSafeSetDemoOne {
public static void main(String[] args) {
Set<String> set = Collections.synchronizedSet(new HashSet<>());
for (int i = 0; i < 30; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString());
System.out.println(set.toString());
}, String.valueOf(i)).start();
}
}
}
(2)第二种解决方案
使用并发编程类CopyOnWriteArraySet替换HashSet
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
public class ContainerSafeSetDemoTwo {
public static void main(String[] args) {
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString());
System.out.println(set.toString());
}, String.valueOf(i)).start();
}
}
}
CopyOnwriteArraySet容器即写时复制容器。往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object []进行copy,复制出一个新的容器object[] newElements,然后往新的容器Object [] newElements里添加元素,添加元素之后,再将原容器的引用指向新的容器setArray(newElements);这样做的好处是可以对CopyOnWrite容器进行并发读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写是不同的容器。
public boolean add(E e) {
return al.addIfAbsent(e);
}
/**
* Appends the element, if not present.
*
* @param e element to be added to this list, if absent
* @return {@code true} if the element was added
*/
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
/**
* A version of addIfAbsent using the strong hint that given
* recent snapshot does not contain e.
*/
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) {
// Optimize for lost race to another addXXX operation
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}