线程安全的集合类
集合类那些时线程安全的?
Vetor 和 HashTable ,Stack 是线程安全的在关键的方法中使用了synchronized
虽然get set方法加入了synchronized 但是如果不能正确使用 也可能会出现线程安全问题
- 如果是多个线程 并发执行set存在 由于synchronized限制 是线程安全的
- 如果多个线程进行一些更复杂的操作 比如判断get的值是某某 在进行set
所以Vector/HashTable这样的集合类虽然加了synchronized也不能保证一定是线程安全的
通四海在单线程的情况下有可能引文synchronized影响到执行的效率
多线程环境使用ArrayList
CopyOnWriteArrayList 写时复制
多个线程同时修改一个变量
如果多个线程修改不同变量 是不是就不安全了
一旦有线程修改 就会把自身复制一份 尤其是修改如果比较耗时 其他线程还是从旧的数据进行读取 一旦完成修改使用新的ArrayList替换旧的ArrayList(本质上就是一个引用的重新赋值 速度极快)
ConcurrentHashMap 线程安全的hash表(重点)
HashMap线程不安全
HashTable线程安全关键方法提供了synchronized 但是进行一些更复杂的操作也会出现线程安全问题
为此Java提供了ConcurrentHashMap线程安全的hash表
两者区别
HashTbale是在方法上直接加上synchronized,就相当于针对this加锁
例如
HashTable<String,String> ht = …;
ht.set(“abc”,“111”);
这里的set会有synchronized(this)
任意针对ht对象的操作 都会涉及到针对this的加锁 此时 如果有很多线程想操作ht就会触发激烈的锁竞争 这些线程最后只能以串行的方式 依次执行
并发程度很低
此时有没有方法能提高并发程度呢?
哈希表的工作原理是
key => hash函数 => 数组下标
当key1 和 key2 => hash =>同一个数组下标时 会发生哈希冲突
有二次探测方法解决 但是在工作中不实用
我们在实际问题中会使用哈希桶(链表)的解决
数组挂上链表的结构
只要控制好链表的长度不要太长 时间复杂度还是o1
如果两个修改操作 是针对两个不同的链表进行修改 是否会存在线程安全问题??
不会! 因为是多线程对于不同的变量进行操作的.
当我们针对同一链表进行修改时 可能会发生线程安全问题
当在同一个链表的不同节点修改时 不会发生线程安全问题
当在同一链表的同一节点修改时很大几率对发生线程安全问题
此时,我们需要针对每个链表加一把锁,在修改那个链表时就给那个链表加锁,所以在针对同一链表操作时会产生锁冲突,在不同链表时不会发生锁冲突(一个hash表上面的链表个数很多,针对两个线程同时操作同一把锁的情况的概率很小,整体锁的开销就大大降低了,比起hash表的全局锁就提高了并发程度)
如何加锁?
由于synchronized随便什么对象都可以加锁,此时我们只需要将每个链表的头节点进行加锁就可以了
这就是
ConcurrentHashMap最重要的改进
- 减少了锁的粒度 大部分情况下不会涉及锁冲突[核心]
- 广泛使用了CAS操作(例如size++)这样也不会产生锁冲突
- 写操作进行了加锁 读操作不加锁了(如果是一个线程读,一个线程写,不会出现问题嘛?最多就是在修改的一瞬间,读到的是一个旧版本/新版本的数据不确定而已.通过一些精密的操作,保证不会读到“半个数据”)
- 针对扩容操作进行了优化,渐进式扩容
HashTable一旦发生扩容,就会立即一口气完成所有元素的搬运 过程先当耗时 (大部分请求很顺畅 某个请求就卡了比较久) 为了解决引入了渐进式扩容
化整为零,当需要进行扩容的时候,会创建出另一个更大的数组,然后把旧的数组上的数据逐渐的往新的数组上搬运.会出现一段时间,旧数组和新数组同时存在
1)新增元素,往新数组上插入
2)删除元素,把就数组的元素给删掉即可.
3)查找元素,新数组旧数组都得查找
4)修改元素,统一把这个元素给搞到新数组上.
于此同时 每个操作都会触发一定程度搬运每次搬运一点就可以保证真题时间不会长每次搬运一点 多次后就逐渐搬运完成了 之后就会把旧数组彻底销毁
HashMap和ConcurrentHashMap的区别 ??
线程安全与不安全的问题
ConcurrentHashMap的分段锁技术