java总结

并发集合的使用建议

一般不需要多线程的情况,只用到HashMap、ArrayList,只要真正用到多线程的时候就一定要考虑同步

。所以这时候才需要考虑同步集合或并发集合

解决方法1:

List list=Collections.synchronizedList(new ArrayList<>());

解决方法2:适用于读多写少的场景下

List list=new CopyOnWriteArrayList<>();

CAS的全称为Compare-And-Swap,它是一条CPU并发原语。它的功能是判断内存某个位置的值是否为预期值

  • ,如果是则更改为新的值,这个过程是原子的。
  • CAS并发原语体现在Java语言中就是sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会
  • 帮实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一
  • 种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原
  • 语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的
  • 数据不一致问题。
  • CAS通俗的解释就是:比较当前工作内存中的值和主内存中的值,如果相同则执行规定操作,否则继续比较直
  • 到主内存和工作内存中的值一致为止.
  • CAS应用
  • CAS有3个操作数,内存值V,旧的预期值A,要修改的更新值B。当且仅当预期值A和内存值V相同时,将内存值
  • V修改为B,否则什么都不做。
  • CAS为什么能保证原子操作呐?
  • 这个就关系到了CAS底层所用到的Unsafe类,Unsafe是CAS的核心类,由于Java方法无法直接访问底层系统,需
  • 要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe
  • 类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖
  • 于Unsafe类的方法。

ConcurrentHashMap实现原理

在日常开发中使用的HashMap是线程不安全的,而线程安全类Hashtable只是简单的在方法上加锁实 * 现线程安全,效率低下,所以在线程安全的环境下通常会使用ConcurrentHashMap

  • 在JDK1.7中ConcurrentHashMap采用了【数组+Segment分段锁+单向链表】的方式实现。
  • 1.Segment分段锁
  • ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap的结构,即内部拥有一个Entry数组,
  • 数组中的每个元素又是一个链表,同时又是一个ReentrantLock(Segment继承了ReentrantLock)。
  • 2.内部结构。
  • ConcurrentHashMap使用分段锁技术实现线程安全行,将数据分成一段一段的存储,然后给每一段数据
  • 配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正
  • 的并发访问。ConcurrentHashMap定位一个元素的过程需要进行两次Hash操作。第一次Hash定位到Segment,
  • 第二次Hash定位到元素所在的链表的头部。
    • 坏处:这一种结构的带来的副作用是Hash的过程要比普通的HashMap要长
    • 好处:写操作的时候可以只对元素所在的Segment进行加锁即可,不会影响到其他的Segment,这样,在最理
  • 想的情况下,ConcurrentHashMap可以最高同时支持Segment数量大小的写操作(刚好这些写操作都非常平均地
  • 分布在所有的Segment上)。所以通过这种结构,ConcurrentHashMap的并发能力可以大大的提高。
  • JDK8中ConcurrentHashMap采用了【数组+链表+红黑树】的实现方式来设计,内部大量采用CAS操作。
  • CAS是compare and swap的缩写,即比较交换。cas是一种基于锁的操作,而且是乐观锁。
  • JDK8中彻底放弃了Segment转而采用的是Node,其设计思想也不再是JDK1.7中的分段锁思想。
  • Node:保存key,value及key的hash值的数据结构。其中value和next都用volatile修饰,保证并发的可见性。
  • 在JDK8中ConcurrentHashMap的结构,由于引入了红黑树,使得ConcurrentHashMap的实现非常复杂,红黑树
  • 是一种性能非常好的二叉查找树,其查找性能为O(logN),但是其实现过程也非常复杂,而且可读性也非常
  • 差,早期完全采用链表结构时Map的查找时间复杂度为O(N),JDK8中ConcurrentHashMap在链表的长度大于
  • 某个阈值的时候会将链表转换成红黑树进一步提高其查找性能。

节点类型

默认桶上的结点就是Node结点。Node只有一个next指针,是一个单链表,提供find方法实现链表查询- 当出现hash冲突时,Node结点会首先以链表的形式链接到table上,当结点数量超过一定数目时,链表会转化为红黑树

TreeNode节点

TreeNode就是红黑树的结点,TreeNode不会直接链接到table[i]——桶上面,而是由TreeBin链接,TreeBin会指向红黑树的根结点。

TreeBin节点

TreeBin会直接链接到table[i]——桶上面,该结点提供了一系列红黑树相关的操作,以及加锁、解锁操作。另外TreeBin提供了一系列的操作- TreeBin(TreeNode b),将以b为头结点的链表转换为红黑树- lockRoot(),对红黑树的根结点加写锁- unlockRoot(),释放写锁- find(int h, Object k),从根结点开始遍历查找,找到相等的结点就返回它,没找到就返回null,当存在写锁时,以链表方式进行查找,不阻塞读锁

ForwardingNode

ForwardingNode在table扩容时使用,内部记录了扩容后的table,即nextTable- 当table需要扩容时,依次遍历table中的每个槽,如果不为null,把所有元素根据hash值放入扩容后的nextTable中,在原table的槽内放置一个ForwardingNode- ForwardingNode是一种临时结点,在扩容进行中才会出现,hash值固定为-1,且不存储实际数据- 如果旧table数组的一个hash桶中全部的结点都迁移到了新table中,则在这个桶中放置一个ForwardingNode- 读操作碰到ForwardingNode时,将操作转发到扩容后的新table数组上去执行;写操作碰见它时,则尝试帮助扩容,扩容是支持多线程一起扩容。

ReservationNode保留结点

在并发场景下、在从Key不存在到插入的时间间隔内,为了防止哈希槽被其他线程抢占,当前线程会使用一个reservationNode节点放到槽中并加锁,从而保证线程安全- hash值固定为-3,不保存实际数据。只在computeIfAbsent和compute这两个函数式API中充当占位符加锁使用

总结: 就算有多个线程同时进行put操作,在初始化数组时使用了乐观锁CAS操作来决定到底是哪个线程有资格进行初始化,其他线程均只能等待。用到的并发技巧:- volatile变量(sizeCtl):它是一个标记位,用来告诉其他线程这个坑位有没有人在,其线程间的可见性由volatile保证。- CAS操作:CAS操作保证了设置sizeCtl标记位的原子性,保证了只有一个线程能设置成功

put操作的线程安全

由于其减小了锁的粒度,若Hash完美不冲突的情况下,可同时支持n个线程同时put操作,n为Node数组大小,在默认大小16下,可以支持最大同时16个线程无竞争同时操作且线程安全。当hash冲突严重时,Node链表越来越长,将导致严重的锁竞争,此时会进行扩容,将Node进行再散列,总结一下用到的并发技巧:- 减小锁粒度:将Node链表的头节点作为锁,若在默认大小16情况下,将有16把锁,大大减小了锁竞争(上下文切换),就像开头所说,将串行的部分最大化缩小,在理想情况下线程的put操作都为并行操作。同时直接锁住头节点,保证了线程安全- Unsafe的getObjectVolatile方法:此方法确保获取到的值为最新。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值