Java并发编程入门与高并发面试(三):线程安全性-原子性-CAS(CAS的ABA问题)
摘要:本文介绍线程的安全性,原子性,java.lang.Number包下的类与CAS操作,synchronized锁,和原子性操作各方法间的对比。
线程安全性
- 线程安全?
- 线程安全性?
- 原子性
- Atomic包中的类与CAS:
- AtomicInteger
- AtomicLong 与 LongAdder
- AtomicBoolean
- AtomicIntegerFieldUpdater
- AtomicStampReference与CAS的ABA问题
- AtomicLongArray
- synchronized
- synchronized 修饰一个代码块
- synchronized 修饰一个方法
- synchronized 修饰一个静态方法
- synchronized 修饰一个类
- 原子性操作各方法间的对比
- Atomic包中的类与CAS:
线程安全?
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。
线程安全性?
线程安全性主要体现在三个方面:原子性、可见性、有序性
- 原子性:提供了互斥访问,同一时刻只能有一个线程来对它进行操作
- 可见性:一个线程对主内存的修改可以及时的被其他线程观察到
- 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序。
基础代码:以下代码用于描述下方的知识点,所有代码均在此代码基础上进行修改。
1 |
public class CountExample { //请求总数 |
原子性
说到原子性,一共有两个方面需要学习一下,一个是JDK中已经提供好的Atomic包,他们均使用了CAS完成线程的原子性操作,另一个是使用锁的机制来处理线程之间的原子性。锁包括:synchronized、Lock
Atomic包中的类与CAS:
我们从最简单的AtomicInteger类来了解什么是CAS
AtomicInteger
上边的示例代码就是通过AtomicInteger类保证了线程的原子性。
那么它是如何保证原子性的呢?我们接下来分析一下它的源码。示例中,对count变量的+1操作,采用的是incrementAndGet方法,此方法的源码中调用了一个名为unsafe.getAndAddInt的方法
1 |
public final int incrementAndGet() { |
而getAndAddInt方法的具体实现为:
1 |
public final int getAndAddInt(Object var1, long var2, int var4) { |
在此方法中,方法参数为要操作的对象Object var1、期望底层当前的数值为var2、要修改的数值var4。定义的var5为真正从底层取出来的值。采用do..while循环的方式去获取底层数值并与期望值进行比较,比较成功才将值进行修改。而这个比较再进行修改的方法就是compareAndSwapInt就是我们所说的CAS,它是一系列的接口,比如下面罗列的几个接口。使用native修饰,是底层的方法。CAS取的是compareAndSwap三个单词的首字母.
另外,示例代码中的count可以理解为JMM中的工作内存,而这里的底层数值即为主内存,如果看过我上一篇文章的盆友就能把这一块的知识点串联起来了。
1 |
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5); |
AtomicLong 与 LongAdder
LongAdder是java8为我们提供的新的类,跟AtomicLong有相同的效果。首先看一下代码实现:
1 |
AtomicLong: |
那么问题来了,为什么有了AtomicLong还要新增一个LongAdder呢?
原因是:CAS底层实现是在一个死循环中不断地尝试修改目标值,直到修改成功。如果竞争不激烈的时候,修改成功率很高,否则失败率很高。在失败的时候,这些重复的原子性操作会耗费性能。
知识点: 对于普通类型的long、double变量,JVM允许将64位的读操作或写操作拆成两个32位的操作。
LongAdder类的实现核心是将热点数据分离,比如说它可以将AtomicLong内部的内部核心数据value分离成一个数组,每个线程访问时,通过hash等算法映射到其中一个数字进行计数,而最终的计数结果则为这个数组的求和累加,其中热点数据value会被分离成多个单元的cell,每个cell独自维护内部的值。当前对象的实际值由所有的cell累计合成,这样热点就进行了有效地分离,并提高了并行度。这相当于将AtomicLong的单点的更新压力分担到各个节点上。在低并发的时候通过对base的直接更新,可以保障和AtomicLong的性能基本一致。而在高并发的时候通过分散提高了性能。
1 |
源码: |
缺点:如果在统计的时候,如果有并发更新,可能会有统计数据有误差。实际使用中在处理高并发计数的时候优先使用LongAdder,而不是AtomicLong在线程竞争很低的时候,使用AtomicLong会简单效率更高一些。比如序列号生成(准确性)
AtomicBoolean
这个类中值得一提的是它包含了一个名为compareAndSet的方法,这个方法可以做到的是控制一个boolean变量在一件事情执行之前为false,事情执行之后变为true。或者也可以理解为可以控制某一件事只让一个线程执行,并仅能执行一次。
他的源码如下:
1 |
public final boolean compareAndSet(boolean expect, boolean update) { |
举例说明:
1 |
//是否发生过 |
AtomicIntegerFieldUpdater
这个类的核心作用是要更新一个指定的类的某一个字段的值。并且这个字段一定要用volatile修饰同时还不能是static的。
举例说明:
1 |
@Slf4j |
AtomicStampReference与CAS的ABA问题
什么是ABA问题?
CAS操作的时候,其他线程将变量的值A改成了B,但是随后又改成了A,本线程在CAS方法中使用期望值A与当前变量进行比较的时候,发现变量的值未发生改变,于是CAS就将变量的值进行了交换操作。但是实际上变量的值已经被其他的变量改变过,这与设计思想是不符合的。所以就有了AtomicStampReference。
1 |
源码: |
AtomicStampReference的处理思想是,每次变量更新的时候,将变量的版本号+1,之前的ABA问题中,变量经过两次操作以后,变量的版本号就会由1变成3,也就是说只要线程对变量进行过操作,变量的版本号就会发生更改。从而解决了ABA问题。
解释一下上边的源码:
类中维护了一个volatile修饰的Pair类型变量current,Pair是一个私有的静态类,current可以理解为底层数值。
compareAndSet方法的参数部分分别为期望的引用、新的引用、期望的版本号、新的版本号。
return的逻辑为判断了期望的引用和版本号是否与底层的引用和版本号相符,并且排除了新的引用和新的版本号与底层的值相同的情况(即不需要修改)的情况(return代码部分3、4行)。条件成立,执行casPair方法,调用CAS操作。
AtomicLongArray
这个类实际上维护了一个Array数组,我们在对数值进行更新的时候,会多一个索引值让我们更新。
原子性,提供了互斥访问,同一时刻只能有一个线程来对它进行操作。那么在java里,保证同一时刻只有一个线程对它进行操作的,除了Atomic包之外,还有锁的机制。JDK提供锁主要分为两种:synchronized和Lock。接下来我们了解一下synchronized。
synchronized
依赖于JVM去实现锁,因此在这个关键字作用对象的作用范围内,都是同一时刻只能有一个线程对其进行操作的。
synchronized是java中的一个关键字,是一种同步锁。它可以修饰的对象主要有四种:
- 修饰代码块:大括号括起来的代码,作用于调用的对象
- 修饰方法:整个方法,作用于调用的对象
- ———————————————————————–
- 修饰静态方法:整个静态方法,作用于所有对象
- 修饰类:括号括起来的部分,作用于所有对象
synchronized 修饰一个代码块
被修饰的代码称为同步语句块,作用的范围是大括号括起来的部分。作用的对象是调用这段代码的对象。
验证:
1 |
public class SynchronizedExample { |
结果:不同对象之间的操作互不影响
synchronized 修饰一个方法
被修饰的方法称为同步方法,作用的范围是大括号括起来的部分,作用的对象是调用这段代码的对象。
验证:
1 |
public class SynchronizedExample |
结果:不同对象之间的操作互不影响
TIPS:
如果当前类是一个父类,子类调用父类的被synchronized修饰的方法,不会携带synchronized属性,因为synchronized不属于方法声明的一部分
synchronized 修饰一个静态方法
作用的范围是synchronized 大括号括起来的部分,作用的对象是这个类的所有对象。
验证:
1 |
public class SynchronizedExample{ |
结果:同一时间只有一个线程可以执行
synchronized 修饰一个类
验证:
1 |
public class SynchronizedExample{ |
结果:同一时间只有一个线程可以执行
原子性操作各方法间的对比
- synchronized:不可中断锁,适合竞争不激烈,可读性好
- Lock:可中断锁,多样化同步,竞争激烈时能维持常态
- Atomic:竞争激烈时能维持常态,比Lock性能好,每次只能同步一个值
经作者授权转载,原文地址:https://blog.csdn.net/jesonjoke/article/details/79837508
Java并发编程入门与高并发面试(三):线程安全性-原子性-CAS(CAS的ABA问题)的更多相关文章
- Java并发编程原理与实战二十:线程安全性问题简单总结
一.出现线程安全性问题的条件 •在多线程的环境下 •必须有共享资源 •对共享资源进行非原子性操作 二.解决线程安全性问题的途径 •synchronized (偏向锁,轻量级锁,重量级锁) •vol ...
- [Java 并发] Java并发编程实践 思维导图 - 第二章 线程安全性
依据<Java并发编程实践>一书整理的思维导图.
- Java并发编程原理与实战八:产生线程安全性问题原因(javap字节码分析)
前面我们说到多线程带来的风险,其中一个很重要的就是安全性,因为其重要性因此,放到本章来进行讲解,那么线程安全性问题产生的原因,我们这节将从底层字节码来进行分析. 一.问题引出 先看一段代码 packa ...
- Java 并发编程(四):如何保证对象的线程安全性
01.前言 先让我吐一句肺腑之言吧,不说出来会憋出内伤的.<Java 并发编程实战>这本书太特么枯燥了,尽管它被奉为并发编程当中的经典之作,但我还是忍不住.因为第四章"对象的组合 ...
- Java并发编程实践读书笔记(1)线程安全性和对象的共享
2.线程的安全性 2.1什么是线程安全 在多个线程访问的时候,程序还能"正确",那就是线程安全的. 无状态(可以理解为没有字段的类)的对象一定是线程安全的. 2.2 原子性 典型的 ...
- Java基础篇——线程、并发编程知识点全面介绍(面试、学习的必备索引)
原创不易,如需转载,请注明出处https://www.cnblogs.com/baixianlong/p/10739579.html,希望大家多多支持!!! 一.线程基础 1.线程与进程 线程是指进程 ...
- Java并发编程入门,看这一篇就够了
Java并发编程一直是Java程序员必须懂但又是很难懂的技术内容.这里不仅仅是指使用简单的多线程编程,或者使用juc的某个类.当然这些都是并发编程的基本知识,除了使用这些工具以外,Java并发编程中涉 ...
- Java与Netty实现高性能高并发
摘要: 1. 背景 1.1. 惊人的性能数据 最近一个圈内朋友通过私信告诉我,通过使用Netty4 + Thrift压缩二进制编解码技术,他们实现了10W TPS(1K的复杂POJO对象)的跨节点远程 ...
- Java生鲜电商平台-高并发的设计与架构
Java生鲜电商平台-高并发的设计与架构 说明:源码下载Java开源生鲜电商平台以及高并发的设计与架构文档 对于高并发的场景来说,比如电商类,o2o,门户,等等互联网类的项目,缓存技术是Java项目中 ...
随机推荐
- [代码审计]PCWAP
为什么想要审计这套源码呐?之前看到某大佬在做反钓鱼网站的时候,发现钓鱼网站的后台用的就是PCWAP,所以我觉得有必要审计一下,顺便记录,打击网络犯罪! 0x00 PCAWAP: PCWAP手机网站建站 ...
- Pmw大控件
Python大控件——Pmw——是合成的控件,以Tkinter控件为基类,是完全在Python内写的.它们可以很方便地增加功能性的应用,而不必写一堆代码.特别是,组合框和内部确认计划的输入字段放在一起 ...
- 常见的nosql数据库有哪些?以及他们的特点与区别?
一.常见的nosql 二.Redis,Memcache,MongoDb的特点 (1).Redis 优点: 1.支持多种数据结构,如 string(字符串). list(双向链表).dict(hash表 ...
- windows下查看rabbitmq服务是否启动
1.命令行进入rabbitmq的安装目录下: 如下图1步骤 2.输入命令 rabbitmqctl1 status 如下图2步骤 3.有时会提示报错,如步骤3 解决办法: 我的电脑 ==> 右键 ...
- JavaScript学习笔记 - 进阶篇(5)- 事件响应
什么是事件 JavaScript 创建动态页面.事件是可以被 JavaScript 侦测到的行为. 网页中的每个元素都可以产生某些可以触发 JavaScript 函数或程序的事件. 比如说,当用户单击 ...
- 8. docker image 的发布 与 docker registry 私有仓库
一.分享image 1.注册 登陆 docker hub https://hub.docker.com/ 2.在本地 使用 docker login 输入 注册的账号密码 进行登陆 3.使用 dock ...
- Web前端学习方向
第一部分 HTML 第一章 职业规划和前景 职业方向规划定位: web前端开发工程师 web网站架构师 自己创业 转岗管理或其他 web前端开发的前景展望: 未来IT行业企业需求最多的人才 结合最新的 ...
- mysql 视图 事务 索引
为什么需要有视图 * 对于复杂的查询,往往是有多个数据表进行关联查询而得到,而这种语句往往比较复杂,也可能非常频繁的使用. 比如这样的SQL语句 select goods.name,goods_cat ...
- day57-mysql-五种约束和sql语句逻辑执行顺序
二.sql语句逻辑执行顺序 () SELECT () DISTINCT <select_list> 去重复 () FROM <left_table> () <join_t ...
- [LC] 93. Restore IP Addresses
Given a string containing only digits, restore it by returning all possible valid IP address combina ...