一,模拟并发代码:

线程不安全的代码

//并发模拟代码
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. Ubuntu下vim打开文件时,提示请按ENTER或其它命令继续

    最近配置了一下vim,重启后,配置生效.但在用vim打开文件的时候,出现了一个问题:每次用vim打开文件的时候,都会提示请按ENTER或其它命令继续.这个真的很烦人.那么怎么把它消除掉呢? 首先要搞清 ...

  2. May 29. 2018 Week 22nd Tuesday

    Nothing is more terrible than ignorance in action. 最可怕的事情莫过于无知而行动. In today's digital age, we can ru ...

  3. March 08th, 2018 Week 10th Thursday

    Easy come, easy go. 易得则易失. Easy come, easy go, I finally undestand the phrase through somewhat hard ...

  4. 关于陌生的依赖模块,如withStyles、react-apollo等

    有自己不认识的依赖,可参考的学习方式: 1.各大技术分享网站的文章(最快) 2.npm官网下的文档(最全)

  5. ES5-ES6-ES7_数值的扩展

    二进制与八进制数值表示法: 二进制用0b, 八进制用0o console.log(0b1010) console.log(0o56) Number.isFinite(i) 判断是否是有限大的数 con ...

  6. mysql 使用正则表达式查询

    SELECT * FROM `qq` where qq_name!='no' and qq_gender='女' and qq_location!='no' and qq_location!='' a ...

  7. java 方法超时

    public void getcd() { logger.info("任务开始!-------------------------------------"); final Exe ...

  8. linux内存源码分析 - 内存回收(匿名页反向映射)

    本文为原创,转载请注明:http://www.cnblogs.com/tolimit/ 概述 看完了内存压缩,最近在看内存回收这块的代码,发现内容有些多,需要分几块去详细说明,首先先说说匿名页的反向映 ...

  9. 对Android系统权限的认识

    Android系统是运行在Linux内核上的,Android与Linux分别有自己的一套严格的安全及权限机制 Android系统权限相关的内容 (一)linux文件系统上的权限 -rwxr-x--x ...

  10. 广师大python学习笔记求派的值

    用python语言算π值并且带有进度条 用python算圆周率π 1.准备第三方库pip 2.利用马青公式求π 3.用python语言编写出求圆周率到任意位的程序如下: from math impor ...