作者回复: 对的
作者回复: 对的,CopyOnWriteArrayList只适合偶尔一两次数据更改的操作。我们很多缓存数据往往是在深夜在没有读操作时,进行修改。这种场景适合使用CopyOnWriteArrayList。
我们先来理解下happens-before规则中,对锁的规则:
一个unLock操作先行发生于后面对同一个锁的lock操作;
也就是说,ConcurrentHashMap中的get如果有锁操作,在put操作之后,get操作是一定能拿到put后的数据;而实际上get操作时没有锁的,也就是说下面这种情况:
void func(){
map.put(key1,value1);
map.get(key1);
.
.
//use key1 value to do something
}
此时,get获取值的可能不是put修改的值,而此时get没有获取到真正要获取的值,此时就是弱一致了。
作者回复: 因为ConcurrentHashMap有些方法是没有锁的,例如get 方法。假设A修改了数据,而B后于A一瞬间去获取数据,有可能拿到的数据是A修改之前的数据。
还有 clear foreach方法在操作时,都有可能存在数据不确定性。
作者回复: 对的,ConcurrentLinkedQueue是基于CAS乐观锁来实现线程安全。ConcurrentLinkedQueue是无界的,所以使用的时候要特别注意内存溢出问题。
作者回复: 1、两者的平均查询复杂度都是O(logn),所以查询性能差不多。而在新增和删除操作,红黑树有平衡操作,但跳跃表也有建立索引层操作。跳跃表的结构简单易懂。
2、这里是基于数据量比较大(例如千万级别)且写入操作多的情况下,ConcurrentSkipListMap性能要比ConcurrentHashMap好一些,并不是在任何情况下都要优于ConcurrentHashMap的。
作者回复: 这跟并发有关系,我们知道ConcurrentHashMap和HashTable都是线程安全的,假设允许key和value为null,有以下代码:
if (map.containsKey(key)) {//代码1
return map.get(key);//代码2
} else {
throw new KeyNotPresentException();
}
当在并发情况下,有两个线程分别在操作map容器,此时线程1在运行以上代码,当线程1运行到代码1与代码2中间时,刚好有另外一个线程2执行了map.remove(key)操作,此时继续运行代码2时,依然会返回null值。而此时的null实际上是map中真实的不存在该key值,应该throw new KeyNotPresentException()的。所以为了保证线程安全,这两个Map容器是不允许key和value为null。
而HashMap是非线程安全的,不存在以上我们所说的并发情况。
编辑回复: 多留言哦~说不定在下一期的加餐中就惊现福利了!😎ི
作者回复: ConcurrentSkipListMap只是key值的升排序,并没有对value进行排序;
CopyOnWrite在副本写时,是需要加锁的。
作者回复: 如果是一个新Node,那么就不能马上看到,虽然Node的数组table被volatile修饰,但是这样只是代表table的引用地址如果被修改,其他线程可以立马看到,并不代表table里的数据被修改立马可以看到。
作者回复: 如果是一个新Node,那么就不能马上看到,虽然Node的数组table被volatile修饰,但是这样只是代表table的引用地址如果被修改,其他线程可以立马看到,并不代表table里的数据被修改立马可以看到。
这里解释的很清楚了,已经存在的value是可以马上看到,但是一个新增的就未必能了。
作者回复: ConcurrentHashMap是volatile关键字保证了可见性。
我们知道Node<k,v>以及Node<k,v>的value是volatile修饰的,所以在一个线程对其进行修改后,另一个线程可以马上看到。
如果是一个新Node,那么就不能马上看到,虽然Node的数组table被volatile修饰,但是这样只是代表table的引用地址如果被修改,其他线程可以立马看到,并不代表table里的数据被修改立马可以看到。—— 《加餐 | 什么是数据的强、弱一致性?》
作者回复: 在留言中已经解释过了,再翻一翻
作者回复: 数据量大的情况下,再好的hash分散,链表的长度也会随着数据量的增加而变长
作者回复: Disruptor是一个高性能队列
作者回复: 在答疑课堂已经做了补充