目录
1.2.4 优化建议 JUC中CopyOnWriteArrayList解决问题的原理
3.2.2 HashMap和ConcurrentHashMap的异同和区别?
3.2.3 JDK1.7到1.8 ConcurrentHashMap分段锁到锁优化的区别?
1. ArrayList
1.1 介绍
HashSet底层数据结构是数组
new ArrayList : 创建了一个空的List, 初始值为10
追溯初始值是从哪里出现的
注意 boolean add()方法没有加锁, 是线程不安全的, 为了保证add的并发性效率,牺牲了安全性
扩容
1.2 ArrayList线程不安全的DEMO
/**
* 集合类不安全问题
* ArrayList
*/
public class ContainerNotSafeDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 1; i <= 3; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
输出:
1.2.1 故障现象
java.util.ConcurrentModificationException非百分百出现异常
1.2.2 导致原因
并发争抢修改导致,A线程正在写入,B线程过来抢夺,导致数据不一致异常,即并发修改异常
举例:一个签名手册,A签名的过程被B打断,B争抢签名手册的时候导致A写错了,出现数据异常
1.2.3 解决方案
/**
* 集合类不安全问题
* ArrayList
*/
public class ContainerNotSafeDemo {
public static void main(String[] args) {
//线程不安全集合
List<String> list = new ArrayList<>();
//解决线程不安全方案:
//方案一 Vector底层是synchronized的集合
//List<String> list = new Vector<>();
//方案二 集合类中的线程的加锁方法
//List<String> list = Collections.synchronizedList(new ArrayList<>());
//方案三 JUC中的方法
//List<String> list = new CopyOnWriteArrayList<>();
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
方案一, 用锁
new ArrayList可改为new Vector, 数据一致性保证了,但并发性下降
方案二, 利用Collections
Collection 接口
Collections 集合接口类,里面也存在Map Set 等集合方法,可使用synchronizedList
方案三, 利用JUC中的类
1.2.4 优化建议 JUC中CopyOnWriteArrayList解决问题的原理
注意代码中的array是被volatile修饰的
下面是add增加元素的源码
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
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); //复制并扩容+1
newElements[len] = e; //赋值新增数据
setArray(newElements); //更新回Array
return true; //操作成功
} finally {
lock.unlock(); //释放锁
}
}
代码解析:
上述代码用的是: 写时复制 (读写分离的思想)
CopyOnWrite容器即写时复制的容器。
往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是:
1. 先将当前容器Object[]进行copy,复制出一个新的容器object[] newElements,
2. 然后新的容器object[] newElements 里添加元素,
3. 添加元素后,将原容器的引用指向新容器setArray(newElements);
这样做的好处是可以对CopyWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyWrite容器也是一种读写分离的思想,读和写不同的容器
2 Set
2.1 HashSet线程不安全
HashSet是线程不安全的,解决方式同ArrayList差不多
/**
* 集合类不安全问题
* HashSet
*/
public class ContainerNotSafeDemo {
public static void main(String[] args) {
//线程不安全集合
Set<String> set = new HashSet<>();
//解决线程不安全方案:
//方案一 集合类中的线程的加锁方法
//Set<String> set = Collections.synchronizedSet(new HashSet<>());
//方案二 JUC中的方法
//Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(set);
}, String.valueOf(i)).start();
}
}
}
输出
2.2 CopyOnWriteArraySet介绍
2.2.1 CopyOnWriteArraySet源码解析
源码:
注意 al = new CopyOnWriteArrayList<E>(); 底层还是用了CopyOnWriteArrayList;
2.3 HashSet源码解析
HashSet底层数据结构是HashMap,可查看源码 map = new HashMap<>();
new HashSet就是创建了初始值16 负载因子0.75的一个标准的HashMap
当使用HashSet<>().add(”“), map是kv键值对,add中为什么只有一个参数?
2.3.1 HashSet为什么add方法只有一个参数?
源码:
如上面的源码所述:
add中的参数被作为map的key来使用,value是一个常量,value是恒定的,并不被关心。
可查看上方2.3 HashSet源码解析中的代码 private static final Object PRESENT = new Object();
3 HashMap
3.1 HashMap是线程不安全的
和上面一样,同样的配方:
/**
* 集合类不安全问题
* ArrayList
* HashSet
* HashMap
*/
public class ContainerNotSafeDemo {
public static void main(String[] args) {
// arrayListNotSafe();
// setNotSafe();
mapNotSafe();
}
public static void mapNotSafe() {
//线程不安全集合
Map<String,String> map = new HashMap<>();
//解决线程不安全方案:
//方案一 集合类中的线程的加锁方法
//Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
//方案二 JUC中的方法
//Map<String,String> map = new ConcurrentHashMap<>();
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8));
System.out.println(map);
}, String.valueOf(i)).start();
}
}
}
3.2 ConcurrentHashMap
3.2.1 put源码
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}
待补充内容,学习后再完善
3.2.2 HashMap和ConcurrentHashMap的异同和区别?
3.2.3 JDK1.7到1.8 ConcurrentHashMap分段锁到锁优化的区别?
4 参考文献
Java面试_高频重点面试题 (第一、二、三季)_ 面试 第1、2、3季_柴林燕_周阳_哔哩哔哩_bilibili