先了解什么是线程安全性:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称为这个类是线程安全的。

线程安全性主要体现在三个方面:

1.原子性:提供了互斥访问,同一时刻只能有一个线程来对它进行操作

2.可见性:一个线程对主内存的修改可以及时的被其他线程观察到

3.有序性:一个线程观察其他线程中的指令执行顺序,由于指令 重排序的存在,该观察结果一般杂乱无序

原子性-Atomic包:他们都是通过CAS来完成原子性的;

先看一段代码:

package com.example.concurrency;

import com.example.concurrency.annotation.NotThreadSafr;
import lombok.extern.slf4j.Slf4j; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore; /**
* @author xiaozhuang
* @date 2022年04月07日 11:55
*/
@Slf4j
@NotThreadSafr // 线程不安全例子
public class ConcurrencyTest {
// 请求访问总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
// 计数的值
public static int count = 0; public static void main(String[] args) throws InterruptedException {
// 线程池
ExecutorService executorService = Executors.newCachedThreadPool();
// 信号量 参数为 运行并发的数目
final Semaphore semaphore = new Semaphore(threadTotal);
// 递减计数器 参数为 请求数量 没执行成功一次会 减1
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);
}
// 没执行完一次 计算器-1
countDownLatch.countDown();
}); }
countDownLatch.await();
// 关闭线程池
executorService.shutdown();
log.info("count:{}", count);
} private static void add() {
count++;
} }

运行时得到的值不准确,达不到预期的5000;

但是我们吧int类型改成Atomic类时,就达到预期的值了,也就是线程安全;

package com.example.concurrency.example.count;

import com.example.concurrency.annotation.NotThreadSafr;
import com.example.concurrency.annotation.ThreadSafe;
import lombok.extern.slf4j.Slf4j; 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; /**
* @author xiaozhuang
* @date 2022年04月07日 11:55
*/
@Slf4j
@ThreadSafe // 线程安全例子
public class CountEample2 {
// 请求访问总数
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);
// 递减计数器 参数为 请求数量 没执行成功一次会 减1
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);
}
// 没执行完一次 计算器-1
countDownLatch.countDown();
}); }
countDownLatch.await();
// 关闭线程池
executorService.shutdown();
// count.get() 获取当前的值
log.info("count:{}", count.get());
} private static void add() {
// 先添加操作再获取值
count.incrementAndGet();
// 先获取值再添加操作
//count.getAndIncrement();
} }

原因是Atomic的incrementAndGet()方法;点进去看到

this为当前对象,valueOffset值偏移

以原子方式将当前值加一;Unsafe.compareAndSwapInt方法处理此问题

再点进去getAndAddInt方法,是下面这段代码:

而CAS就是compareAndSwapInt的缩写;

被native标识的方法,也就是java底层的方法;

现在对这段代码解析一波:首先传过来的    ①:var1 就是我们之前传过来的count对象    ②:var2 是当前的值    ③:var4 的值就是1  ④:var5 是通过底层方法获取底层当前的值;

假设现在要做的操作是2+1:var1 就是我们之前传过来的count对象; 那么var2就是2, var4是1;如果没有别的线程来处理var1 对象的时候,这时候  var5返回就是2,也就是this.getIntVolatie(var1,var2)得到的值是2;

然后是compareAndSwapInt这个方法的调用:var1-当前count对象,var2-值为2,var5-值为底层传过来的2,var4-值为1;后面的var5+var4 (var5为底层取出来的值,var4要增加的值1),等于3;

当前方法希望达成的目标是,对这个count对象,当前的var2的值与底层的var5的值相同的情况下,把它更新成后面的值(也就是var5+var4);

当我们刚进来这个方法的时候,var2的值为2,再进行更新变成3的时候,可能会被别的线程更改,所有他会判断期望的值,也就是var2的值等于var5的时候,才会更新成后面的值,如果不相等,则会重新再var1这个对象中取出var2这个值,再去跟var5对比,相等就做后面的操作;

再举个例子:在进行2+1操作的时候,刚进来var2的值为2,在进行更新变成3的时候,被其他线程所更改了,此时底层的var5的值就是3了,当前线程拿var2来跟var5对比(也就是2与3对比),是不相等的;那么它就是再去var1这个对象中把var2值取出来,此时取出来的就是3了(假设期间没被其他线程更改),var2与var5都是3,比对相等,然后进行后面的更新,var5+var4--> 3+1=4;

下面介绍一下Atomic包中 AtomicRerence类的使用

package com.example.concurrency.example.atomic;

import com.example.concurrency.annotation.NotThreadSafr;
import com.example.concurrency.annotation.ThreadSafe;
import lombok.extern.slf4j.Slf4j; 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.AtomicReference; @Slf4j
@ThreadSafe
public class AtomicEample1 {
// AtomicReference类
private static AtomicReference<Integer>count=new AtomicReference<>(0); public static void main(String[] args) {
// 值为0时,赋值为2 下面的以此类推
count.compareAndSet(0,2);
count.compareAndSet(0,1);
count.compareAndSet(1,3);
count.compareAndSet(2,4);
count.compareAndSet(3,5);
log.info("count:{}",count.get());
} }

输出为:4--->首先是0 更新为2, 当其为2时,更新为4;其他的因为第一个参数对不上,所有不会执行。

Atomic包中 AtomicIntegerFieldUpdater类的使用:

package com.example.concurrency.example.atomic;

import com.example.concurrency.annotation.ThreadSafe;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; @Slf4j
@ThreadSafe
public class AtomicEample2 {
// 针对integer的操作 泛型为需要更新的对象
// 第一个参数为类的class,第二参数必须由 volatile修饰的名字 就是下面的count属性
private static AtomicIntegerFieldUpdater<AtomicEample2> updater =
AtomicIntegerFieldUpdater.newUpdater(AtomicEample2.class, "count");
@Getter
public volatile int count = 100; private static AtomicEample2 eample2 = new AtomicEample2(); public static void main(String[] args) {
// 如果当前对象的属性值为100 就修改为150
if (updater.compareAndSet(eample2, 100, 150)) {
log.info("updater success one:{}", eample2.getCount());
}
// 如果当前对象的属性值为200 就修改为150
if (updater.compareAndSet(eample2, 200, 150)) {
log.info("updater success two:{}", eample2.getCount());
} else {
log.info("updater failed:{}", eample2.getCount());
}
}
}

它是以原子性去更新某个类的实例指定的某个字段,这里也就是count字段,并且这个字段必须被volatile修饰且不能是static的;

输出为:updater success one:150与 updater failed:150--->值为100更新为150;  值已经是150,固if不成立走else输出150;

再介绍一下AtomicStampReference类关于CAS的ABA问题:

首先什么是CAS的ABA:就是本线程在进行CAS操作的时候,其他线程将变量的值从A改成了B,然后又改回了A;本线程在使用期望值A去与当前变量比较时,发现A变成没有变,于是CAS就将A值进行了交换操作;此时,实际该值已经被其他线程改变过了。

CAS的解决思路:每次变量更新的时候将变量的版本号进行+1操作,那么之前的就是A是1版本-->改成 B是2版本-->再改回 A是3版本;只有变量被某个线程修改过,版本号就会发生递增变化,从而解决了ABA问题。

package com.example.concurrency.example.atomic;

import com.example.concurrency.annotation.ThreadSafe;
import lombok.extern.slf4j.Slf4j; import java.util.concurrent.atomic.AtomicStampedReference; /**
* @author xiaozhuang
* @date 2022年04月07日 22:31
*/
@Slf4j
@ThreadSafe
public class AtomicEample3 {
// initalRef为初始值 initialStamp为初始版本号
private static AtomicStampedReference<Integer> stampedReference =
new AtomicStampedReference<>(100, 1); public static void main(String[] args) {
new Thread(() -> {
log.info("张三查-初始值为:{},版本号为:{}", stampedReference.getReference(), stampedReference.getStamp());
stampedReference.compareAndSet(100, 127, stampedReference.getStamp(),
stampedReference.getStamp() + 1);
log.info("当前值为:{},版本号为:{}", stampedReference.getReference(), stampedReference.getStamp());
stampedReference.compareAndSet(127, 100, stampedReference.getStamp(),
stampedReference.getStamp() + 1);
log.info("当前值为:{},版本号为:{}", stampedReference.getReference(), stampedReference.getStamp());
}, "小明").start();
new Thread(() -> {
try {
Thread.sleep(200);
log.info("李四查-初始值为:{},版本号为:{}", stampedReference.getReference(), stampedReference.getStamp());
boolean isSuccess = stampedReference.compareAndSet(100, 126, stampedReference.getStamp(),
stampedReference.getStamp() + 1);
log.info("是否更新成功:{},当前值为:{},版本号为:{}", isSuccess ? "success" : "failed", stampedReference.getReference(), stampedReference.getStamp()); } catch (InterruptedException e) {
e.printStackTrace();
} },"李四").start();
}
}

点进去compareAndSet方法中

四个参数:① 预期值  ②更新的值 ③预期版本号 ④更新的版本号

对于这份Pair,点进去发现它是AtomicStampedReference的内部类,发现它里面含有对象的值和版本号

可以看到compareAndSet返回的是更新是否成功的布尔值,那么它是在哪里进行更新操作的呢?可以看到这个方法里最底下的 casPair方法,点进去可以看到它是用底层的CAS将值和版本号进行更新的,而CAS怎么做的,在上面已经分析过了。

再介绍一下AtomicLongArrayBoolean类:

package com.example.concurrency.example.atomic;

import com.example.concurrency.annotation.ThreadSafe;
import lombok.extern.slf4j.Slf4j; 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 java.util.concurrent.atomic.AtomicReference; @Slf4j
@ThreadSafe
public class AtomicEample4 {
// AtomicBoolean 参数为 默认值
private static AtomicBoolean isHappend = new AtomicBoolean(false);
// 请求访问总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200; public static void main(String[] args) throws InterruptedException {
// 线程池
ExecutorService executorService = Executors.newCachedThreadPool();
// 信号量 参数为 运行并发的数目
final Semaphore semaphore = new Semaphore(threadTotal);
// 递减计数器 参数为 请求数量 没执行成功一次会 减1
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);
}
// 没执行完一次 计算器-1
countDownLatch.countDown();
}); }
countDownLatch.await();
// 关闭线程池
executorService.shutdown();
log.info("isHappend:{}", isHappend.get());
} private static void test() {
// 预期值是false 更新为true
if (isHappend.compareAndSet(false, true)) {
log.info("update sueccess");
}
}
}

可以看到我们请求总数为5000,并发数200,但是这段代码只输出了一次,原因是 第一次是false,与预期值一致,改为true了;那么接下来不管判断再多次,它都是true,永远跟期望值为false不同,所以永远也修改不了了;在实际应用中,让一段代码只执行一次,不会重复,可以使用它来做。

顺带说一下AtomicLong 与LongAddr的区别以及AtomicLongArray的使用:

LongAddr的优点:LongAdder在AtomicLong的基础上将单点的更新压力分散到各个节点,在并发量低的时候通过对一个base的值的直接更新可以很好的保                                障和AtomicLong的性能基本保持一致,而在并发量高的时候则是通过分散提高了性能。
                   缺点:LongAdder在统计的时候如果有并发更新,可能导致统计的数据有误差。
AtomicLongArray:与其他Atomic类基本差不多,不同的是多了个Array,在进行取值,更新操作时,都要根据索引来维护。

线程安全性-原子性之Atomic包的更多相关文章

  1. Java并发编程入门与高并发面试(三):线程安全性-原子性-CAS(CAS的ABA问题)

    摘要:本文介绍线程的安全性,原子性,java.lang.Number包下的类与CAS操作,synchronized锁,和原子性操作各方法间的对比. 线程安全性 线程安全? 线程安全性? 原子性 Ato ...

  2. 并发与高并发(七)-线程安全性-原子性-atomic

    一.线程安全性定义 定义:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程 ...

  3. 4-3 线程安全性-原子性-synchronized

    原子性它提供了互斥访问,同一时刻只能有一个线程来对它进行操作.能保证同一时刻只有一个线程来对其进行操作的,除了Atomic包之外,还有锁.JDK提供锁主要分两种,synchronized是一个Java ...

  4. 4-2 线程安全性-原子性-atomic-2

    AtomicReference和AtomicLong.AtomicInteger很像,方法也基本上是一样的,然后我们通过引用Integer来做一个简单的例子. com.mmall.concurrenc ...

  5. 线程安全性-原子性之synchronized锁

    原子性提供了互斥访问:同一时刻只能有一个线程进行操作: 除了Atomic包类之外,还有锁可以实现此功能: synchronized:  java关键字,依赖于jvm实现锁功能,被此关键字所修饰的,都是 ...

  6. 4-1 线程安全性-原子性-atomic-1

    我们发现在不做任何同步的情况下,我们计算的累加结果是错误的. com.mmall.concurrency.example.count.CountExample2 C:\Users\ZHONGZHENH ...

  7. 并发与高并发(八)-线程安全性-原子性-synchronized

    前言 闲暇时刻,谈一下曾经在多线程教程中接触的同步锁synchronized,相当于复习一遍吧. 主要介绍 synchronized:依赖JVM Lock:依赖特殊的CPU指令,代码实现,Reetra ...

  8. Java线程安全性-原子性工具对比

    synchronized 不可中断锁,适合竞争不激烈的场景,可读性好,竞争激烈时性能下降很快 Lock 可中断锁,多样化同步,竞争激烈时能维持常态 Atomic 竞争激烈时能维持常态,比Lock性能还 ...

  9. java并发编程笔记(三)——线程安全性

    java并发编程笔记(三)--线程安全性 线程安全性: ​ 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现 ...

随机推荐

  1. 适配手机端rpx像素

    <script src="static/js/adaptive.js"></script> <script type="text/javas ...

  2. setTimeout中第一个参数

    永远不要传递字符串作为setTimeout的第一个参数!!!记住第一个参数只允许函数,或者匿名函数!因为传递字符串有陷阱啊!!它就是披着羊皮的eval啊!!而且上下文会变成全局啊!! 第一个参数为可执 ...

  3. SpringMVC注解环境搭建

    基本步骤 新建Maven项目(Web) 导入依赖 配置web.xml 配置springmvc配置文件 编写Controller 创建view页面 部署并启动Tomcat 开始搭建 新建Maven项目( ...

  4. spring-bean依赖注入-02(通过p命名空间注入)

    上一篇博客讲述了为什么使用spring依赖注入,怎么注入,详见 spring-bean依赖注入-01(等你来点击) 废话不多说,开始使用p命名空间进行set注入 使用另外一种注入方式是这样的(具体实现 ...

  5. jdk-1.8环境变量配置

    1.首先下载好jdk-1.8的安装包. 这个安装也是傻瓜式安装,一直下一步即可.一定要记得中间你所设置的安装路径 2.切记 切记 jdk的安装路径 ! 3.右键"此电脑",点击最下 ...

  6. Java学习day22

    今天跟着视频学习了JScroll面板,图片按钮以及单多选框 这是一个图片按钮程序 很多网站还有软件的图标兼容按钮的功能,如果只有button是无法实现该功能的这时候就需要使用到图标按钮 文本框没有办法 ...

  7. 使用Camera API https://developer.mozilla.org/zh-CN/docs/Web/Guide/API/Camera

    使用Camera API 在本文章中 获取到所拍摄照片的引用 在网页中展示图片 完整的示例代码 HTML页面: JavaScript文件: 浏览器兼容性 通过Camera API,你可以使用手机的摄像 ...

  8. Apache Doris 单节点(可多节点)Docker集群制作教程

    集群制作Author:苏奕嘉脚本研发Author:种益调研测试Author:杨春东 前言 Apache Doris是当下非常火热和流行的MPP架构OLAP数据库,很多同学想自学/测试Doris的使用和 ...

  9. 盘点提高国内访问 Github 的速度的 9 种方案

    开源Linux 长按二维码加关注~ 上一篇:一行代码如何隐藏Linux进程? 来源:https://urlify.cn/IFzQRb GitHub 镜像访问 GitHub文件加速 Github 加速下 ...

  10. 『现学现忘』Git基础 — 21、git diff命令

    目录 1.git diff 命令说明 2.比较工作区与暂存区中文件的差别 3.比较暂存区与本地库中文件的差别 4.总结git diff命令常见用法 5.总结 1.git diff 命令说明 在comm ...