锁:
https://javabetter.cn/thread/thread-bring-some-problem.html#%E5%B0%8F%E7%BB%93
https://javaguide.cn/distributed-system/distributed-transaction.html
锁,看这个,写的超级好
串联:
hashmap courrenthashmap
1 HashMap 底层实现
https://javabetter.cn/collection/hashmap.html#_01%E3%80%81hash-%E6%96%B9%E6%B3%95%E7%9A%84%E5%8E%9F%E7%90%86
基于 数组+链表/红黑树。
1、HashMap是基于哈希表实现的,而哈希表的底层是数组加上链表的形式。
2、数组内 存连续查询 效率高,链表内存分散 增删改 效率高。
3、哈希表的默认长度是16,编号从0开始。图中编号0-4的长方形代表了一个数组,箭头指向的代表了一个一个的链表。
存储原理:
1、用HashMap存储数据( put(key,value) )时,会先操作key调用.hashcode()方法得出hash值,
然后再通过哈希算法 转换成数组的一个下标,对应的就是在数组上的的存储位置。
2、如果该位置没有数据,则直接存储。如果该位置有数据,
遍历该 位置上 链表上的所有数据,并且通过equals()方法来比对每个数据的key。
如果key相同的话,会将链表上该位置的数据进行覆盖
如果key不相同的话,尾插法数据存储在链表尾部。
-
当链表上的节点个数(数据个数)大于等于8时, 数组长度不小于64的时候,
链表数据结构自动进行树化转化成红黑树,当链表上的数据小于等于6个时,又会自动退化成链表
4、如果数组的长度小于64的话链表数据个数达到了8的话也不会转化成红黑树,而是先进行扩容,直到数组长度达到64
HashMap 的扩容机制
扩容是通过 resize 方法来实现的,JDK 8 中融入了红黑树(链表长度超过 8 的时候,会将链表转化为红黑树来提高查询效率)
先将数组的长度扩大一倍,然后将原来的元素重新散列到新的数组中。
HashMap 中不断添加元素时, 会自动进行扩容操作(条件是元素数量达到负载因子(load factor)乘以数组长度时),以保证其存储的元素数量不会超出其容量限制。
加载因子越小,填满的数据就越少,哈希冲突的几率就减少了,但浪费了空间,而且还会提高扩容的触发几率;
加载因子越大,填满的数据就越多,空间利用率就高,但哈希冲突的几率就变大了。
HashMap 的容量是 16,加载因子是 0.75,当 16*0.75=12 时,会触发扩容机制。
TreeMap
HashMap 在遍历时是无序的,因此如果需要有序遍历,可以使用TreeMap,它是利用红黑树实现的
LinkedHashMap
HashMap 是无序的,LinkedHashMap 是可以维持插入顺序的。
在 LinkedHashMap 中,链表中的节点顺序是按照插入顺序维护的。当使用 put() 方法向 LinkedHashMap 中添加键值对时,会将新节点插入到链表的尾部,并更新 before 和 after 属性,以保证链表的顺序关系。
由于 LinkedHashMap 要维护双向链表,所以 LinkedHashMap 在插入、删除操作的时候,花费的时间要比 HashMap 多一些。
HashMap 本身并不保证键值对的顺序,如果我们需要按照插入顺序或访问顺序来遍历键值对,就需要使用 LinkedHashMap 了。
TreeMap , HashMap 和 LinkedHashMap ,那如何从它们三个中间选择呢?
需要考虑以下因素:
如果需要按照键key排序,则可以使用 TreeMap;如果不需要排序,则可以使用 HashMap 或 LinkedHashMap。
如果需要保持插入顺序,则可以使用 LinkedHashMap;如果不需要保持插入顺序,则可以使用 TreeMap 或 HashMap。
如果需要高效的查找,则可以使用 LinkedHashMap 或 HashMap,因为它们的查找操作的时间复杂度为 O(1),而是 TreeMap 是 O(log n)。
**
锁:
ConcurrentHashMap底层实现(线程安全)**
**
Node数组+链表/红黑树, 红黑树是为了提高查找效率
红黑树:节点 与 根节点对比,小的放左边,大的放右边。
1.8 版本舍弃了 segment,并且使用了大量的 synchronized,以及 CAS 无锁操作以保证 ConcurrentHashMap 的线程安全性。
为什么不用 ReentrantLock 而是 synchronzied 呢?
实际上,synchronzied 做了很多的优化,这个我们前面也讲过了,包括偏向锁、轻量级锁、重量级锁,可以依次向上升级锁状态,因此,synchronized 相较于 ReentrantLock 的性能其实差不多,甚至在某些情况更优。
而在 JDK 1.8 中,ConcurrentHashMap 主要做了两个优化:
同 HashMap 一样,链表也会在长度达到 8 的时候转化为红黑树,这样可以提升大量冲突时候的查询效率;
以某个位置的头结点(链表的头结点或红黑树的 root 结点)为锁,配合自旋+ CAS 避免不必要的锁开销,进一步提升并发性能。
CAS 算法: U.compareAndSwapXXXX 方法
用于保证线程安全性,这是一种乐观策略:假设每一次操作都不会产生冲突,当且仅当冲突发生的时候再去尝试。
很多属性都是用 volatile 关键字修饰的,也是为了保证内存可见性
Collections.synchronizedList()
ArrayList 是一个线程不安全的容器,如果在多线程环境下使用,需要手动加锁,或者使用 Collections.synchronizedList() 方法将其转换为线程安全的容器
CopyOnWriteArrayList 是线程安全的,可以在多线程环境下使用。CopyOnWriteArrayList 遵循写时复制的原则,每当对列表进行修改(例如添加、删除或更改元素)时,都会创建列表的一个新副本,这个新副本会替换旧的列表,而对旧列表的所有读取操作仍然可以继续。
由于在修改时创建了新的副本,所以读取操作不需要锁定。这使得在多读取者和少写入者的情况下读取操作非常高效。当然,由于每次写操作都会创建一个新的数组副本,所以会增加存储和时间的开销。如果写操作非常频繁,性能会受到影响
CopyOnWriteArrayList 的缺点
CopyOnWrite 容器有很多优点,但是同时也存在两个问题,即内存占用问题和数据一致性问题。所以在开发的时候需要特别注意。
内存占用问题:因为 CopyOnWrite 的写时复制机制,在进行写操作的时候,内存里会同时有两个对象,旧的对象和新写入的对象,分析 add 方法的时候大家都看到了。
如果这些对象占用的内存比较大,比如说 200M 左右,那么再写入 100M 数据进去,内存就会占用 600M,那么这时候就会造成频繁的 minor GC 和 major GC。
数据一致性问题:CopyOnWrite 容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用 CopyOnWrite 容器,最好通过 ReentrantReadWriteLock 自定义一个的列表。
如果读线程能够立即读到新添加的数据就叫数据实时性