集合不安全

ArrayList不安全

ArrayList的方法没加synchronized关键字,多线程下是不安全的
java.util.concurrentModificationError

解决方案

1 new Vector();
2 Collections.synchronizedList(new ArrayList<>());
3 new CopyOnWriteArrayList<>();

其原理就是add,remove方法
加reentranlock,利用空间换时间,复制原来的object数组为array2
将 e 赋值给 array2的最后一个空间里面
将新数组 array2 赋值给 CopyOnWriteArrayList 中的 array

优化建议
在读多写少的时候推荐使用 CopeOnWriteArrayList 这个类

HashSet不安全

HashSet同样是不安全的。
解决方法

1 Collections.synchronizedSet(new HashSet<>());
2 new CopyOnWriteArraySet<>();

其底层创建的就是CopyOnWriteArrayList。
注:HashSet底层也是HashMap,key是HashSet存储的元素,value是一个object类型常量

HashMap不安全

面试6连击

1 谈谈你理解的 HashMap,讲讲其中的 get put 过程。
2 1.8 做了什么优化?
3 是线程安全的嘛?
4 不安全会导致哪些问题?
5 如何解决?有没有线程安全的并发容器?
6 ConcurrentHashMap 是如何实现的? 1.7、1.8 实现有何不同?为什么这么做?

ConcurrentHashMap

HashMap不安全
put resize 会形成环状数据结构
HashSet安全,但是效率低。

ConcurrentHashMap

1.7 segment分段锁+ReentrantLock

初始化ConcurrentHashMap的时候,会初始化一个Segment数组,容量为16,而每个Segment都继承了ReentrantLock类,Segment内部又有一个table数组,每个table数组里的索引数据,又对应着一个Node链表.
首先,当我们使用put方法的时候,是对我们的key进行hash拿到一个整型,然后将整型对16取模,拿到对应的Segment,之后调用Segment的put方法,然后上锁,请注意,这里lock()的时候其实是this.lock(),也就是说,每个Segment的锁是分开的
其中一个上锁不会影响另一个,此时也就代表了我可以有十六个线程进来,而ReentrantLock上锁的时候如果只有一个线程进来,是不会有线程挂起的操作的,也就是说只需要在AQS里使用CAS改变一个state的值为1,此时就能对代码进行操作,这样一来,我们等于将并发量/16了.
put get size 方法如何实现?

1.8 cas+synchronized

java 线程不安全类 java线程不安全的集合_数组

Synchronized锁的对象,请记住,Synchronized是靠对象的对象头和此对象对应的monitor来保证上锁的,也就是对象头里的重量级锁标志指向了monitor,而monitor呢,内部则保存了一个当前线程,也就是抢到了锁的线程.

那么这里的这个f是什么呢?它是Node链表里的每一个Node,也就是说,Synchronized是将每一个Node对象作为了一个锁,这样做的好处是什么呢?将锁细化了,也就是说,除非两个线程同时操作一个Node,注意,是一个Node而不是一个Node链表哦,那么才会争抢同一把锁.

如果使用ReentrantLock其实也可以将锁细化成这样的,只要让Node类继承ReentrantLock就行了,这样的话调用f.lock()就能做到和Synchronized(f)同样的效果,但为什么不这样做呢?

请大家试想一下,锁已经被细化到这种程度了,那么出现并发争抢的可能性还高吗?还有就是,哪怕出现争抢了,只要线程可以在30到50次自旋里拿到锁,那么Synchronized就不会升级为重量级锁,而等待的线程也就不用被挂起,我们也就少了挂起和唤醒这个上下文切换的过程开销.

但如果是ReentrantLock呢?它则只有在线程没有抢到锁,然后新建Node节点后再尝试一次而已,不会自旋,而是直接被挂起,这样一来,我们就很容易会多出线程上下文开销的代价.当然,你也可以使用tryLock(),但是这样又出现了一个问题,你怎么知道tryLock的时间呢?在时间范围里还好,假如超过了呢?

所以,在锁被细化到如此程度上,使用Synchronized是最好的选择了.这里再补充一句,Synchronized和ReentrantLock他们的开销差距是在释放锁时唤醒线程的数量,Synchronized是唤醒锁池里所有的线程+刚好来访问的线程,而ReentrantLock则是当前线程后进来的第一个线程+刚好来访问的线程.

如果是线程并发量不大的情况下,那么Synchronized因为自旋锁,偏向锁,轻量级锁的原因,不用将等待线程挂起,偏向锁甚至不用自旋,所以在这种情况下要比ReentrantLock高效