并发与高并发(七)-线程安全性-原子性-atomic
前言
何为原子性?它又是通过什么原理来控制线程安全的?这里主要介绍有关Atomic原子性操作的几个类的使用场景和方法。
主体概要
AtomicInteger
AtomicLong
LongAdder
AtomicReference
AtomicIntegerFieldUpdater
AtomicStampedReference
AtomicBoolean
主体内容
一、线程安全性定义
定义:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。
线程安全性主要体现在三个方面:原子性、可见性、有序性:
- 原子性:提供了互斥访问,同一时刻只能有一个线程来对它进行操作
- 可见性:一个线程对主内存的修改可以及时地被其他线程观察到
- 有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序
使用示例:
1.AtomicInteger
package com.controller.atomic; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger; import com.annoations.ThreadSafe; import lombok.extern.slf4j.Slf4j;
@Slf4j
@ThreadSafe
public class AtomicIntegerTest {
//请求数
public static int clientTotal=5000;
//并发数
public static int threadTotal=200;
//计数值
public static AtomicInteger count= new AtomicInteger(0); public static void main(String[] args) throws InterruptedException{
//创建线程池
ExecutorService executorService = Executors.newCachedThreadPool();
//定义信号量(允许并发数)
final Semaphore semaphore = new Semaphore(threadTotal);
//定义计数器
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for(int i =0;i<clientTotal;i++){
executorService.execute(()->{
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception",e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}",count.get());
} private static void add(){
//先增加操作,再获取当前的值
count.incrementAndGet();
//先获取当前的值,在增加操作
//count.getAndIncrement();
}
}
重点在于.incrementAndGet()方法中的unsafe.getAndAddInt()方法,如下所示:
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
使用了一个叫unsafe的类,看它里面getAndAddInt的实现,标红的compareAndSwapInt方法需要注意,首先这个“paramObject”是指当前调用的对象,即上面例子中的count对象,而paramLong是当前的值,比如这里想执行2+1=3的操作,paramLong就等于2,paramInt就等于1。i是从调用这个getIntVolatile方法得到底层当前的值。那么compareAndSwapInt(paramObject, paramLong, i, i + paramInt))中变量值可以解释为compareAndSwapInt(count,2, 2, 2 +1)),如果paramLong和底层的i值相同的话,将其值更新为i+paramInt。也就是期望的值与底层值相等,才会执行+1操作,最后把底层的值覆盖掉,这就是CAS的核心。
public final int getAndAddInt(Object paramObject, long paramLong, int paramInt)
{
int i;
do
{
i = getIntVolatile(paramObject, paramLong);
} while (!compareAndSwapInt(paramObject, paramLong, i, i + paramInt));
return i;
}
2.AtomicLong
package com.controller.atomic; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicLong; import com.annoations.ThreadSafe; import lombok.extern.slf4j.Slf4j;
@Slf4j
@ThreadSafe
public class AtomicLongTest {
//请求数
public static int clientTotal=5000;
//并发数
public static int threadTotal=200;
//计数值
public static AtomicLong count= new AtomicLong(0); public static void main(String[] args) throws InterruptedException{
//创建线程池
ExecutorService executorService = Executors.newCachedThreadPool();
//定义信号量(允许并发数)
final Semaphore semaphore = new Semaphore(threadTotal);
//定义计数器
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for(int i =0;i<clientTotal;i++){
executorService.execute(()->{
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception",e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}",count.get());
} private static void add(){
//先增加操作,再获取当前的值
count.incrementAndGet();
//先获取当前的值,在增加操作
//count.getAndIncrement();
}
}
3.LongAdder
package com.controller.atomic; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.LongAdder; import lombok.extern.slf4j.Slf4j;
@Slf4j
public class LongAddrTest {
//请求数
public static int clientTotal=5000;
//并发数
public static int threadTotal=200;
//计数值
public static LongAdder count= new LongAdder(); public static void main(String[] args) throws InterruptedException{
//创建线程池
ExecutorService executorService = Executors.newCachedThreadPool();
//定义信号量(允许并发数)
final Semaphore semaphore = new Semaphore(threadTotal);
//定义计数器
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for(int i =0;i<clientTotal;i++){
executorService.execute(()->{
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception",e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}",count);
} private static void add(){
//先增加操作,再获取当前的值
count.increment();
//先获取当前的值,在增加操作
//count.getAndIncrement();
}
}
LongAdder 与 AtomicLong有什么区别?
(1)AtomicLong 是基于 CAS 方式自旋更新的;LongAdder 是把 value 分成若干cell,并发量低的时候,直接 CAS 更新值,成功即结束。并发量高的情况,CAS更新某个cell值和需要时对cell数据扩容,成功结束;更新失败自旋 CAS 更新 cell值。取值的时候,调用 sum() 方法进行每个cell累加。
(2)AtomicLong 包含有原子性的读、写结合的api;LongAdder 没有原子性的读、写结合的api,能保证结果最终一致性。
低并发场景AtomicLong 和 LongAdder 性能相似,高并发场景 LongAdder 性能优于 AtomicLong。
4.AtomicReference
package com.controller.atomic;
import java.util.concurrent.atomic.AtomicReference;
import com.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@ThreadSafe
public class AtomicReferenceTest {
private static AtomicReference<Integer> count = new AtomicReference<>(0); public static void main(String[] args){
//如果值为0,则结果为2
count.compareAndSet(0, 2);//2
//如果值为0,则结果为1
count.compareAndSet(0, 1);//no
//如果值为1,则结果为3
count.compareAndSet(1, 3);//no
//如果值为2,则结果为4
count.compareAndSet(2, 4);//4
//如果值为3,则结果为5
count.compareAndSet(3, 5);//no
log.info("count:{}",count.get());
}
}
5.AtomicIntegerFieldUpdater
package com.controller.atomic; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class AtomicIntegerFileUpdaterTest { private static AtomicIntegerFieldUpdater<AtomicIntegerFileUpdaterTest> updater = AtomicIntegerFieldUpdater.newUpdater(AtomicIntegerFileUpdaterTest.class, "count");
@Getter
public volatile int count=100; public static void main(String[] args){
AtomicIntegerFileUpdaterTest AtomicIntegerFileUpdaterTest = new AtomicIntegerFileUpdaterTest(); if(updater.compareAndSet(AtomicIntegerFileUpdaterTest, 100, 120)){
log.info("update success,{}",AtomicIntegerFileUpdaterTest.getCount());
}
if(updater.compareAndSet(AtomicIntegerFileUpdaterTest, 100, 120)){
log.info("update success,{}",AtomicIntegerFileUpdaterTest.getCount());
}else{
log.info("update failed,{}",AtomicIntegerFileUpdaterTest.getCount());
}
}
}
加粗部分指代的是当前类"AtomicIntegerFileUpdaterTest"下的“count”变量,而count必须被volatile关键字修饰,不能被static修饰。
程序执行的结果为:
update success,120
update failed,120
compareAndSet(param1,param2,param3)指代的是如果param1中被AtomicIntegerFieldUpdater定义的对象的变量等于param2,则将其更新为param3。否则不更新。
6.AtomicStampedReference
解决CAS的ABA问题,每次增加操作,在原有版本号基础上加一。
关于ABA问题,这里作一个详细介绍:
在多线程场景下CAS会出现ABA问题,关于ABA问题这里简单科普下,例如有2个线程同时对同一个值(初始值为A)进行CAS操作,这三个线程如下
.线程1,期望值为A,欲更新的值为B
.线程2,期望值为A,欲更新的值为B
线程1抢先获得CPU时间片,而线程2因为其他原因阻塞了,线程1取值与期望的A值比较,发现相等然后将值更新为B,然后这个时候出现了线程3,期望值为B,欲更新的值为A,线程3取值与期望的值B比较,发现相等则将值更新为A,此时线程2从阻塞中恢复,并且获得了CPU时间片,这时候线程2取值与期望的值A比较,发现相等则将值更新为B,虽然线程2也完成了操作,但是线程2并不知道值已经经过了A->B->A的变化过程。 ABA问题带来的危害: 小明在提款机,提取了50元,因为提款机问题,有两个线程,同时把余额从100变为50
线程1(提款机):获取当前值100,期望更新为50,
线程2(提款机):获取当前值100,期望更新为50,
线程1成功执行,线程2某种原因block了,这时,某人给小明汇款50
线程3(默认):获取当前值50,期望更新为100,
这时候线程3成功执行,余额变为100,
线程2从Block中恢复,获取到的也是100,compare之后,继续更新余额为50!!!
此时可以看到,实际余额应该为100(-+),但是实际上变为了50(-+-)这就是ABA问题带来的成功提交。
解决方法: 在变量前面加上版本号,每次变量更新的时候变量的版本号都+,即A->B->A就变成了1A->2B->3A。
其中的compareAndSet()方法如下:
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
7.AtomicBoolean
package com.controller.atomic; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean; import lombok.extern.slf4j.Slf4j; @Slf4j
public class AtomicBooleanTest {
private static AtomicBoolean isHappend = new AtomicBoolean(false);
//请求数
public static int clientTotal=5000;
//并发数
public static int threadTotal=200; public static void main(String[] args) throws Exception{
//创建线程池
ExecutorService executorService = Executors.newCachedThreadPool();
//定义信号量(允许并发数)
final Semaphore semaphore = new Semaphore(threadTotal);
//定义计数器
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for(int i =0;i<clientTotal;i++){
executorService.execute(()->{
try {
semaphore.acquire();
test();
semaphore.release();
} catch (Exception e) {
log.error("exception",e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("isHappend:{}",isHappend.get());
}
public static void test(){
if(isHappend.compareAndSet(false, true)){
log.info("execute..");
}
}
}
结果:true
这个类的示例用于在并发情况下,想只执行一次某段逻辑,绝对不会重复。
并发与高并发(七)-线程安全性-原子性-atomic的更多相关文章
- Java并发编程入门与高并发面试(三):线程安全性-原子性-CAS(CAS的ABA问题)
摘要:本文介绍线程的安全性,原子性,java.lang.Number包下的类与CAS操作,synchronized锁,和原子性操作各方法间的对比. 线程安全性 线程安全? 线程安全性? 原子性 Ato ...
- [ 高并发]Java高并发编程系列第二篇--线程同步
高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...
- Java并发编程(一):并发与高并发等基础概念
并发概念 同时拥有两个或者多个线程,如果程序在单核处理器上运行,多个线程将交替地换入或者换出内存,这些线程是同时存在的,每个线程都处于执行过程中的某个状态.如果运行在多核处理器上,程序中的每个线程都将 ...
- Java并发(理论知识)—— 线程安全性
1.什么是线程安全性 当多个线 ...
- 线程安全性-原子性之Atomic包
先了解什么是线程安全性:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称为这个类是线程 ...
- 4-3 线程安全性-原子性-synchronized
原子性它提供了互斥访问,同一时刻只能有一个线程来对它进行操作.能保证同一时刻只有一个线程来对其进行操作的,除了Atomic包之外,还有锁.JDK提供锁主要分两种,synchronized是一个Java ...
- 4-1 线程安全性-原子性-atomic-1
我们发现在不做任何同步的情况下,我们计算的累加结果是错误的. com.mmall.concurrency.example.count.CountExample2 C:\Users\ZHONGZHENH ...
- 【高并发】高并发环境下如何优化Tomcat配置?看完我懂了!
写在前面 Tomcat作为最常用的Java Web服务器,随着并发量越来越高,Tomcat的性能会急剧下降,那有没有什么方法来优化Tomcat在高并发环境下的性能呢? Tomcat运行模式 Tomca ...
- 【高并发】高并发环境下如何防止Tomcat内存溢出?看完我懂了!!
写在前面 随着系统并发量越来越高,Tomcat所占用的内存就会越来越大,如果对Tomcat的内存管理不当,则可能会引发Tomcat内存溢出的问题,那么,如何防止Tomcat内存溢出呢?我们今天就来一起 ...
随机推荐
- “~" 的用法
“~" 的用法 let arr = ['weixin','qq','weibo'] console.log(arr.indexOf('aa'),~arr.indexOf('aa'),'aa' ...
- (转)解决windows解决windows 7 部分程序图标显示不正常的问题
刚解决计算机的管理选项打开出现问题,又发现系统里部分程序的快捷图标显示不出了, 曾在xp里也出现过同样的问题,常理推断,如果系统没有被病毒破坏那可能就是系统图标缓存出现问题 因此,双管齐下,一边检查系 ...
- javascript 创建私有变的三个方法
//方法一 function m() { //这是私有变量 let p = 10; //这是私有方法 function pr() { return false; } //读取或者设置 私有变量和方法 ...
- 050-PHP除法运算
<?php $n=10/3; //除法运算 echo $n; //输出变量n的值 ?>
- 记录一次SQLServer 2019 MDS问题的排查
问题表象: MDS网页里看不到任何建立的模型和实体. 用Excel add in连接,提示SQLServer授权过期. 但实际上SQLServer是企业版,目前并没有过期. 背景分析: 我们的环境是从 ...
- jar包学习
jar: java的压缩包,主要用于存储类文件,或者配置文件等. 命令格式: jar -cf 包名.jar 包目录 解压缩: jar -xvf 包名.jar 将jar包目录列表重定向到一个文件中: j ...
- 【LGR-(-8)】洛谷入门赛 #5 题解
比赛链接 9道题. 注:题目名称中链接为题目链接,题号中链接为比赛内链接 题目编号 洛谷题号 题目名称 题目难度 A P5713 [深基3.例5]洛谷团队系统 \(\color{red}{入门}\) ...
- Elasticsearch 删除文档
章节 Elasticsearch 基本概念 Elasticsearch 安装 Elasticsearch 使用集群 Elasticsearch 健康检查 Elasticsearch 列出索引 Elas ...
- ACM-AK吧!少年
题目描述:AK吧!少年 AK is an ACM competition finished all of the problems. AC This problem is one of the ste ...
- office(CVE-2012-0158)漏洞分析报告
2019/9/12 1.漏洞复现 ①发现崩溃 ②找到漏洞所在的函数,下断点,重新跑起来,单步调试,找到栈被改写的地方 ③分析该函数 把MSCOMCTL拖入IDA,查看该函数代码 ④查看调用栈,回溯. ...