CAS

Java底层类和源码分析系列-ConcurrentHashMap源码分析

会有一股神秘感。 提交于 2020-04-17 17:52:34
要点 ConcurrentHashMap是HashMap的线程安全版本; 不允许[key,value]为null; 比Hashtable锁粒度更细; 采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树; 负载因子0.75; 默认初始化容量16; put时当前bucket为空时,使用CAS操作,将Node放入对应的bucket中; put时出现hash冲突,则采用synchronized; 查询操作不加锁,因此ConcurrentHashMap不是强一致性; ConcurrentHashMap内部采用的锁有synchronized、CAS、自旋锁、分段锁、volatile; 定义 public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>    implements ConcurrentMap<K,V>, Serializable 其中接口ConcurrentMap<K, V>来自Map<K, V>,添加了一些函数式接口方法,比如: default void forEach(BiConsumer<? super K, ? super V> action) default void replaceAll(BiFunction<? super K, ?

ConcurrentHashMap总结

不羁岁月 提交于 2020-04-17 03:25:23
【推荐阅读】微服务还能火多久?>>> 并发编程实践中,ConcurrentHashMap是一个经常被使用的数据结构,相比于Hashtable以及Collections.synchronizedMap(),ConcurrentHashMap在线程安全的基础上提供了更好的写并发能力,但同时降低了对读一致性的要求(这点好像CAP理论啊 O(∩_∩)O)。ConcurrentHashMap的设计与实现非常精巧,大量的利用了volatile,final,CAS等lock-free技术来减少锁竞争对于性能的影响,无论对于Java并发编程的学习还是Java内存模型的理解,ConcurrentHashMap的设计以及源码都值得非常仔细的阅读与揣摩。 这篇日志记录了自己对ConcurrentHashMap的一些总结,由于JDK6,7,8中实现都不同,需要分开阐述在不同版本中的ConcurrentHashMap。 之前已经在 ConcurrentHashMap原理分析 中解释了ConcurrentHashMap的原理,主要是从代码的角度来阐述是源码是如何写的,本文仍然从源码出发,挑选个人觉得重要的点(会用红色标注)再次进行回顾,以及阐述ConcurrentHashMap的一些注意点。 1. JDK6与JDK7中的实现 1.1 设计思路 ConcurrentHashMap采用了 分段锁 的设计

ConcurrentHashMap中节点数目并发统计的实现原理

与世无争的帅哥 提交于 2020-04-17 00:18:15
【推荐阅读】微服务还能火多久?>>> 前言: 前段时间又看了一遍ConcurrentHashMap的源码,对该并发容器的底层实现原理有了更进一步的了解,本想写一篇关于ConcurrentHashMap的put方法所涉及的初始化以及扩容操作等的源码解析的,但是这类文章在各平台上实在是太多了,写了感觉意义不是很大。但学了东西,还是想着尽量能够输出点什么,所以就打算稍微写写点偏门的,关于ConcurrentHashMap中统计节点数目的size()方法的实现原理。由于JDK 1.7和1.8中ConcurrentHashMap的源码发生了较大的变化,size()方法的底层实现也发生了变动。为此,本文将会通过 对比两个版本的size()方法的底层实现 来加深对ConcurrentHashMap的理解。 关键字: 源码、JDK1.7、JDK1.8、ConcurrentHashMap、LongAdder 思考: 在了解具体实现原理之前,我们可以 先自己思考 该如何实现并发容器中的节点数目统计问题。 一种很自然的想法是, 用一个成员变量(size)来统计容器的节点数目, 每次对容器进行put或者remove操作时,都对该成员变量(size)进行线程安全的更新。这样,当要获取容器的节点数目时,直接返回该值即可。 这样的计数方法在返回结果的时候,速度很快,但是在统计的过程中 存在着一个很明显的问题

Java并发工具类之LongAdder原理总结 转

假如想象 提交于 2020-04-15 16:41:11
【推荐阅读】微服务还能火多久?>>> 出处: Java并发工具类之LongAdder原理总结 LongAdder实现原理图   高并发下N多线程同时去操作一个变量会造成大量线程CAS失败,然后处于自旋状态,导致严重浪费CPU资源,降低了并发性。既然AtomicLong性能问题是由于过多线程同时去竞争同一个变量的更新而降低的,那么如果把一个变量分解为多个变量,让同样多的线程去竞争多个资源。    LongAdder则是内部维护一个Cells数组,每个Cell里面有一个初始值为0的long型变量,在同等并发量的情况下,争夺单个变量的线程会减少,这是变相的减少了争夺共享资源的并发量,另外多个线程在争夺同一个原子变量时候,如果失败并不是自旋CAS重试,而是尝试获取其他原子变量的锁,最后当获取当前值时候是把所有变量的值累加后再加上base的值返回的。   LongAdder维护了要给延迟初始化的原子性更新数组和一个基值变量base数组的大小保持是2的N次方大小,数组表的下标使用每个线程的hashcode值的掩码表示,数组里面的变量实体是Cell类型。   Cell 类型是Atomic的一个改进,用来减少缓存的争用,对于大多数原子操作字节填充是浪费的,因为原子操作都是无规律的分散在内存中进行的,多个原子性操作彼此之间是没有接触的,但是原子性数组元素彼此相邻存放将能经常共享缓存行,也就是伪共享

Redis 21问,你接得住不?

ε祈祈猫儿з 提交于 2020-04-15 16:29:54
【推荐阅读】微服务还能火多久?>>> 作者:菜鸟小于 https://www.cnblogs.com/Young111/p/11518346.html 1.什么是redis? Redis 是一个基于内存的高性能key-value数据库。 2.Reids的特点 Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库统统加载在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存。因为是纯内存操作,Redis的性能非常出色,每秒可以处理超过 10万次读写操作,是已知性能最快的Key-Value DB。 Redis的出色之处不仅仅是性能,Redis最大的魅力是支持保存多种数据结构,此外单个value的最大限制是1GB,不像 memcached只能保存1MB的数据,因此Redis可以用来实现很多有用的功能,比方说用他的List来做FIFO双向链表,实现一个轻量级的高性 能消息队列服务,用他的Set可以做高性能的tag系统等等。另外Redis也可以对存入的Key-Value设置expire时间,因此也可以被当作一 个功能加强版的memcached来用。 Redis的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上。 3.使用redis有哪些好处? 1

java 之 Synchronized 锁深度解读(和朋友探讨后的总结和验证)

时光毁灭记忆、已成空白 提交于 2020-04-14 03:54:45
【今日推荐】:为什么一到面试就懵逼!>>> 首先一句话自己概述一下自己的理解, 一种由JVM实现的互斥同步锁、非公平锁 。 底层通过该 monitorenter 和 monitorexit 两个字节码指令实现。 在执行到monitorenter命令时,首先尝试获取对象锁, 成功,把锁的计数器加一,这时候对象就是锁定状态(非0),最后执行monitorexit,会将锁计数器减一来实现解锁。 注意:非零或者本身持有对象锁都可以获取对象锁,这个主要用来解决死锁问题,同时实现了锁重入 锁的本质是monitorenter和 monitorexit指令的一个 Reference 类型参数,即要锁定和解锁的对象。 jdk6之前,monitor完全依赖底层操作系统的互斥锁来实现(也因为此不同的底层实现有差别), jdk6开始对锁做了许多优化,说之前先需要知道一个知识点,就是线程的挂起和唤醒需要操作系统协助,在用户态和内核态切换是很耗费资源的,我下面都用一句话来解释 首先就是自旋锁,就是发现对象被锁,死循环等一会再试一下。 锁消除:在运行时发现不可能存在共享数据竞争的锁进行消除。 锁粗化:在一系列连续动作都对同一对象反复枷锁和解锁,造成性能损耗,自动扩大锁范围 轻量级锁:相对原生互斥锁(悲观锁),他就是一种乐观锁,可以参考cas操作 倾向锁:提前预知倾向执行的线程,然后消除它的锁 比较

java Atomic类使用

本秂侑毒 提交于 2020-04-13 21:44:39
【今日推荐】:为什么一到面试就懵逼!>>> JUC包中除了锁,还提供了原子操作类来实现线程对临界资源的互斥访问。 Atomic包中提供了多种类型的原子操作类: 它们都是CAS(compareAndSwap)来实现原子性。 1.原子基本类型 用于原子更新基本类型,包括以下三类: AtomicBoolean:原子更新布尔类型 AtomicInteger:原子更新整数类型 AtomicLong:原子更新长整数类型 AtomicInteger 常用方法: int incrementAndGet():将当前值加1并返回新值。 int decrementAndGet():将当前值减1并返回新值 int updateAndGet(IntUnaryOperator updateFunction):将当前值原子执行用户自定义的操作后并返回新值。(updateFunction是用户自定义操作) public class AtomicIntegerTest { public static void main ( String [ ] args ) { AtomicInteger integer = new AtomicInteger ( 0 ) ; //递增 System . out . println ( "递增后的值: " + integer . incrementAndGet ( ) ) ; /

synchronized在JDK6做了哪些优化

纵然是瞬间 提交于 2020-04-13 20:35:38
【今日推荐】:为什么一到面试就懵逼!>>> 记得在大学中那个时候刚开始学习java, 需要遇到多线程需要加锁的操作时,不管不顾全部都用synchronized,相对于当时的我们来说synchronized是这么的神奇而又强大,那个时候我们赋予它一个名字“同步”,也成为了我们解决多线程情况的百试不爽的良药。但是,随着我们学习的进行我们知道synchronized是一个重量级锁,相对于Lock,它会显得那么笨重,以至于我们认为它不是那么的高效而慢慢摒弃它。 但是,随着Javs SE 1.6对synchronized进行的各种优化后,synchronized并不会显得那么重了。下面跟随LZ一起来探索synchronized的实现机制、Java是如何对它进行了优化、锁优化机制、锁的存储结构和升级过程; synchronized 作用 synchronized是java 中的一个关键字, synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。 Java中每一个对象都可以作为锁,这是synchronized实现同步的基础: 普通同步方法,锁是当前实例对象 静态同步方法,锁是当前类的class对象 同步方法块,锁是括号里面的对象 当一个线程访问同步代码块时,它首先是需要得到锁才能执行同步代码

CAS 以及它在java中的体现

限于喜欢 提交于 2020-04-13 20:35:17
【今日推荐】:为什么一到面试就懵逼!>>> Compare And Swap:比较和交换 v(内存位置) a(旧的期望值) b(改变之后的值) 数据在修改之前,旧的预期值和v中的值一致时,才能修改 java中Atomic相关类就是使用的这种算法实现的。 它和Synchronized相对: Synchoronized属于悲观锁,认为并发数据操作是一个大概率事件(总有刁民想害朕),我操作的时候,得把数据锁上,别人谁也别想动 而CAS属于乐观锁,它认为并发数据操作是小概率事件,你操作数据之后我再核查一遍,现在的数据和我修改的时候,读取的数据是否一致就没事了,通过比较和交换的方式进行数据修改。 拿 AtomicInteger 的 incrementAndGet 方法举例,这个方法就是给当前对象的数值+1,但是保证了线程安全: public void test(){ AtomicInteger atomicInteger = new AtomicInteger(1); atomicInteger.incrementAndGet (); Integer integer = 1; integer = integer + 1; // 以上两种操作,结果是一样的 } 看看CAS再JDK1.8中是怎么实现的: public final int incrementAndGet() { //

阿里面试题:ConcurrentHashMap为什么是线程安全的?

老子叫甜甜 提交于 2020-04-12 17:25:22
阿里面试题:ConcurrentHashMap为什么是线程安全的? ConcurrentHashMap,其实是线程安全的HashMap,所以阅读ConcurrentHashMap,建议 先阅读一下两篇介绍HashMap的文章 你真的懂大厂面试题:HashMap吗? jdk1.7 HashMap中的致命错误:循环链表 jdk1.7 ConcurrentHashMap jdk1.7 ConcurrentHashMap数据结构 jdk1.7 ConcurrentHashMap是由一个Segment数组和多个HashEntry数组组成 其实就是将HashMap分为多个小HashMap,每个Segment元素维护一个小HashMap,目的是 锁分离 ,本来实现同步,直接可以是对整个HashMap加锁,但是加锁粒度太大,影响并发性能,所以变换成此结构,仅仅对Segment元素加锁,降低锁粒度,提高并发性能。 初始化过程 由于变换成 Segment数组+HashEntry数组 ,所以初始化时,需要依次对Segment数组和小 HashEntry数组初始化 Segment数组初始化 初始化时,使用右移一位,乘以2的计算方式,保证ssize是2的幂次方,小于指定参数 concurrencyLevel 的最大2的幂次方。 int sshift = 0 ; //记录Segment数组大小 int