一,模拟并发代码:

线程不安全的代码

//并发模拟代码
public class CountExample {
//请求总数
public static int clientTotal = 5000;
//同时并发执行的线程数
public static int threadTotal = 200;
//全局变量
public static int count = 0;
public static void main(String[] args) {
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 (InterruptedException e){
System.out.println("exception");
e.printStackTrace();
}
//闭锁,每执行一次add()操作,请求数就减一
countDownLatch.countDown();
});
} //等待上面的线程都执行完毕,countDown的值减为0,然后才向下执行主线程
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印count的值
System.out.println("count:"+count); //关闭线程池
executorService.shutdown(); } private static void add(){
count++;
}
}

二,二.原子性-Atomic包
1.AtomicInteger类中提供了incrementAndGet方法;
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
2.incrementAndGet方法又调用了Unsafe类的getAndAddInt方法
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

return var5;
}
3.getAndAddInt方法又是如何保证原子性的呢?该方法调用了compareAndSwapInt方法(就是我们说的CAS)
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
compareAndSwapInt方法是native方法,这个方法是java底层的方法(不是通过java实现的)
4.原理解析:

public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

return var5;
}
Object var1:传进来的AtomicInteger对象
long var2:是传进来的值,当前要进行加一的值 (比如要进行2+1的操作, var2就是2)
int var4:是传进来的值,进行自增要加上的值 (比如要进行2+1的操作, var4就是1)
int var5:是通过调用底层的方法this.getIntVolatile(var1, var2);得到的底层当前的值
while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)):
通过do{} while()不停的将当前对象的传进来的值和底层的值进行比较,
如果相同就将底层的值更新为:var5+var4(加一的操作),
如果不相同,就重新再从底层取一次值,然后再进行比较,这就是CAS的核心。
帮助理解:
把AtomicInteger里面存的值看成是工作内存中的值
把底层的值看成是主内存中的值。在多线程中,工作内存中的值和主内存中的值会出现不一样的情况。

线程安全的代码:
//线程安全的并发
public class CountExample2 {
//请求总数
public static int clientTotal = 5000;
//同时并发执行的线程数
public static int threadTotal = 200;
//全局变量
public static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) {
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 (InterruptedException e){
System.out.println("exception");
e.printStackTrace();
}
//闭锁,每执行一次add()操作,请求数就减一
countDownLatch.countDown();
});
} //等待上面的线程都执行完毕,countDown的值减为0,然后才向下执行主线程
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印count的值
System.out.println("count:"+count.get());
//关闭线程池
executorService.shutdown();
}
private static void add(){
//count++;
count.incrementAndGet();
}
}

二,AtomicBoolean的使用:
1.需求:
当第一个线程进来的时候,我希望做一些初始化的操作,当第一个线程做完初始化的操作,我需要标记初始化的工作已经做完了,其他后进来的线程不需要做这个初始化的工作了,并且必须保证只被做了一次
有问题的代码:
public class CountExample4 {
//请求总数
public static int clientTotal = 50000;
//同时并发执行的线程数
public static int threadTotal = 2000;
public static boolean isHappened = false; public static void main(String[] args) {
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 (InterruptedException e){
System.out.println("exception");
e.printStackTrace();
}
//闭锁,每执行一次add()操作,请求数就减一
countDownLatch.countDown();
});
} //等待上面的线程都执行完毕,countDown的值减为0,然后才向下执行主线程
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印count的值
System.out.println("isHappened:"+isHappened);
//关闭线程池
executorService.shutdown();
} private static void test(){
//如果是false,就更新为true,并且我希望只有一个线程执行了判断条件里的代码
if (isHappened == false){
isHappened = true;
System.out.println("execute");
}
}
}

执行结果:

execute
execute
isHappened:true

注意: 

当有大量的线程同时进来时,我们不能保证execute只被执行了一次。这时,我们可以考虑使用AtomicBoolean类

public class CountExample3 {
//请求总数
public static int clientTotal = 5000;
//同时并发执行的线程数
public static int threadTotal = 200;
public static AtomicBoolean isHappened = new AtomicBoolean(false);
public static void main(String[] args) {
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 (InterruptedException e){
System.out.println("exception");
e.printStackTrace();
}
//闭锁,每执行一次add()操作,请求数就减一
countDownLatch.countDown();
});
} //等待上面的线程都执行完毕,countDown的值减为0,然后才向下执行主线程
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印count的值
System.out.println("isHappened:"+isHappened.get());
//关闭线程池
executorService.shutdown();
} private static void test(){
//如果是false,就更新为true
if (isHappened.compareAndSet(false,true)){
System.out.println("execute");
}
}

三,CAS中的ABA问题
描述:在CAS操作时,其他线程将变量的值从A改成了B,然后又将B改回了A。
解决思路:每次变量改变时,将变量的版本号加1,只要变量被修改过,变量的版本号就会发生递增变化
使用的类:AtomicStampedReference,
调用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)));
}
stamp是每次更新时就维护的, 通过对比来判断是不是一个版本号,expectedStamp == current.stamp

 
 

并发包学习之-atomic包的更多相关文章

  1. 并发包学习(一)-Atomic包小记

    此篇是J.U.C学习的第一篇Atomic包相关的内容,希望此篇总结能对自己的基础有所提升.本文总结来源自<Java并发编程的艺术>第七章并配以自己的实践理解.如有错误还请指正. 一.案例分 ...

  2. golang语言中sync/atomic包的学习与使用

    package main; import ( "sync/atomic" "fmt" "sync" ) //atomic包提供了底层的原子级 ...

  3. JUC学习笔记--Atomic原子类

    J.U.C 框架学习顺序 http://blog.csdn.net/chen7253886/article/details/52769111 Atomic 原子操作类包 Atomic包 主要是在多线程 ...

  4. Java中的Atomic包使用指南

    Atomic包介绍 在Atomic包里一共有12个类,四种原子更新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新字段.Atomic包里的类基本都是使用Unsafe实现的包装类. 原 ...

  5. 并发编程(一)—— volatile关键字和 atomic包

    本文将讲解volatile关键字和 atomic包,为什么放到一起讲呢,主要是因为这两个可以解决并发编程中的原子性.可见性.有序性,让我们一起来看看吧. Java内存模型 JMM(java内存模型) ...

  6. JDK中Concurrent包介绍及使用(包含atomic包/lock包/并发容器/执行器)

    Java Concurrent并发包概括  https://blog.csdn.net/u012232736/article/details/79919450 Java中的Atomic包使用指南   ...

  7. Java中的Atomic包

    Atomic包的作用 方便程序员在多线程环境下,无锁的进行原子操作 Atomic包核心 Atomic包里的类基本都是使用Unsafe实现的包装类,核心操作是CAS原子操作: 关于CAS compare ...

  8. java.util.concurrent.atomic 包详解

    Atomic包的作用: 方便程序员在多线程环境下,无锁的进行原子操作 Atomic包核心: Atomic包里的类基本都是使用Unsafe实现的包装类,核心操作是CAS原子操作 关于CAS compar ...

  9. Java中Atomic包的实现原理及应用

    1. 同步问题的提出 假设我们使用一个双核处理器执行A和B两个线程,核1执行A线程,而核2执行B线程,这两个线程现在都要对名为obj的对象的成员变量i进行加1操作,假设i的初始值为0,理论上两个线程运 ...

随机推荐

  1. 第十四届智能车队员培训 I/O的使用 数据方向寄存器和数据寄存器的配置 MC9S12D64处理器

    I/O的使用 数据方向寄存器和数据寄存器的配置 I/O输入输出的使用: 数据方向寄存器与数据寄存器 寄存器的概念: 寄存器,是集成电路中非常重要的一种存储单元,通常由触发器组成.在集成电路设计中,寄存 ...

  2. 网站出现403 Forbidden

    1, 你在一定时间内过多地访问此网站(一般是用采集程序),被防火墙拒绝访问了 2, 网站域名解析到了空间,但空间未绑定此域名 3, 你的网页脚本文件在当前目录下没有执行权限 4, 服务器繁忙,同一IP ...

  3. java.util.LinkedHashMap cannot be cast to xxx 和 net.sf.ezmorph.bean.MorphDynaBean cannot be cast to xxx

    java.util.LinkedHashMap cannot be cast to com.entity.Person 使用mybatis, resultMap映射的是实体类Person, 查询出来的 ...

  4. tushare 开源数据包的使用

    tushare 使用 python开源金融接口包: tushare.org/trading.html#d2 安装: pip install tushare import tushare as ts # ...

  5. Django-rest-framework 接口实现 Serializer 使用

    Django接口实现 DRF 使用 以下模块 实现 json数据 序列化 博客: https://www.cnblogs.com/liwenzhou/p/9959979.html Django RES ...

  6. Centos 6.7 安装mongodb

    下载mongodb  https://www.mongodb.com/download-center#community 2.解压文件 tar -zxvf mongodb-linux-x86_64-3 ...

  7. docker: read tcp 192.168.7.235:36512->54.230.212.9:443: read: connection reset by peer.

    在学习rancher的时候去下载rancher/agent镜像的时候,出现报错:docker: read tcp 192.168.7.235:36512->54.230.212.9:443: r ...

  8. Grid布局指南

    简介 CSS网格布局(又称“网格”),是一种二维网格布局系统.CSS在处理网页布局方面一直做的不是很好.一开始我们用的是table(表格)布局,然后用float(浮动),position(定位)和in ...

  9. C# Lambda 表达式学习之(四):动态构建类似于 c => c.Age == 2 || c.Age == 5 || c => c.Age == 17 等等一个或多个 OrElse 的表达式

    可能你还感兴趣: 1. C# Lambda 表达式学习之(一):得到一个类的字段(Field)或属性(Property)名,强类型得到 2. C# Lambda 表达式学习之(二):LambdaExp ...

  10. Python入门学习:1.变量和简单的数据类型

    python入门学习:1.变量和简单的数据类型 关键点:变量.字符串.数字 1.1 变量的命名和使用1.2 字符串1.3 数字1.4 注释 1.1 变量的命名和使用   变量,顾名思义是一个可变的量, ...