AtomicReference和AtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,而AtomicReference则对应普通的对象引用。也就是它可以保证你在修改对象引用时的线程安全性。在介绍AtomicReference的同时,我希望同时提出一个有关原子操作的逻辑上的不足。

之前我们说过,线程判断被修改对象是否可以正确写入的条件是对象的当前值和期望是否一致。这个逻辑从一般意义上来说是正确的。但有可能出现一个小小的例外,就是当你获得对象当前数据后,在准备修改为新值前,对象的值被其他线程连续修改了2次,而经过这2次修改后,对象的值又恢复为旧值。这样,当前线程就无法正确判断这个对象究竟是否被修改过。如图4.2所示,显示了这种情况。

图4.2 对象值被反复修改回原数据

一般来说,发生这种情况的概率很小。而且即使发生了,可能也不是什么大问题。比如,我们只是简单得要做一个数值加法,即使在我取得期望值后,这个数字被不断的修改,只要它最终改回了我的期望值,我的加法计算就不会出错。也就是说,当你修改的对象没有过程的状态信息,所有的信息都只保存于对象的数值本身。

但是,在现实中,还可能存在另外一种场景。就是我们是否能修改对象的值,不仅取决于当前值,还和对象的过程变化有关,这时,AtomicReference就无能为力了。

打一个比方,如果有一家蛋糕店,为了挽留客户,绝对为贵宾卡里余额小于20元的客户一次性赠送20元,刺激消费者充值和消费。但条件是,每一位客户只能被赠送一次。

现在,我们就来模拟这个场景,为了演示AtomicReference,我在这里使用AtomicReference实现这个功能。首先,我们模拟用户账户余额。

定义用户账户余额:

static AtomicReference<Integer> money=newAtomicReference<Integer>();
// 设置账户初始值小于20,显然这是一个需要被充值的账户
money.set(19);

  

接着,我们需要若干个后台线程,它们不断扫描数据,并为满足条件的客户充值。

01 //模拟多个线程同时更新后台数据库,为用户充值
02 for(int i = 0 ; i < 3 ; i++) {
03 new Thread(){
04 publicvoid run() {
05 while(true){
06 while(true){
07 Integer m=money.get();
08 if(m<20){
09 if(money.compareAndSet(m, m+20)){
10 System.out.println("余额小于20元,充值成功,余额:"+money.get()+"元");
11 break;
12 }
13 }else{
14 //System.out.println("余额大于20元,无需充值");
15 break ;
16 }
17 }
18 }
19 }
20 }.start();
21 }

  

上述代码第8行,判断用户余额并给予赠予金额。如果已经被其他用户处理,那么当前线程就会失败。因此,可以确保用户只会被充值一次。

此时,如果很不幸的,用户正好正在进行消费,就在赠予金额到账的同时,他进行了一次消费,使得总金额又小于20元,并且正好累计消费了20元。使得消费、赠予后的金额等于消费前、赠予前的金额。这时,后台的赠予进程就会误以为这个账户还没有赠予,所以,存在被多次赠予的可能。下面,模拟了这个消费线程:

01 //用户消费线程,模拟消费行为
02 new Thread() {
03 public voidrun() {
04 for(inti=0;i<100;i++){
05 while(true){
06 Integer m=money.get();
07 if(m>10){
08 System.out.println("大于10元");
09 if(money.compareAndSet(m, m-10)){
10 System.out.println("成功消费10元,余额:"+money.get());
11 break;
12 }
13 }else{
14 System.out.println("没有足够的金额");
15 break;
16 }
17 }
18 try{Thread.sleep(100);} catch (InterruptedException e) {}
19 }
20 }
21 }.start();

  上述代码中,消费者只要贵宾卡里的钱大于10元,就会立即进行一次10元的消费。执行上述程序,得到的输出如下:

余额小于20元,充值成功,余额:39元
大于10元
成功消费10元,余额:29
大于10元
成功消费10元,余额:19
余额小于20元,充值成功,余额:39元
大于10元
成功消费10元,余额:29
大于10元
成功消费10元,余额:39
余额小于20元,充值成功,余额:39元

从这一段输出中,可以看到,这个账户被先后反复多次充值。其原因正是因为账户余额被反复修改,修改后的值等于原有的数值。使得CAS操作无法正确判断当前数据状态。

虽然说这种情况出现的概率不大,但是依然是有可能的出现的。因此,当业务上确实可能出现这种情况时,我们也必须多加防范。体贴的JDK也已经为我们考虑到了这种情况,使用AtomicStampedReference就可以很好的解决这个问题。

推荐本书:

【实战Java高并发程序设计 2】无锁的对象引用:AtomicReference的更多相关文章

  1. 【实战Java高并发程序设计6】挑战无锁算法:无锁的Vector实现

    [实战Java高并发程序设计 1]Java中的指针:Unsafe类 [实战Java高并发程序设计 2]无锁的对象引用:AtomicReference [实战Java高并发程序设计 3]带有时间戳的对象 ...

  2. 【实战Java高并发程序设计 4】数组也能无锁:AtomicIntegerArray

    除了提供基本数据类型外,JDK还为我们准备了数组等复合结构.当前可用的原子数组有:AtomicIntegerArray.AtomicLongArray和AtomicReferenceArray,分别表 ...

  3. 【实战Java高并发程序设计 7】让线程之间互相帮助--SynchronousQueue的实现

    [实战Java高并发程序设计 1]Java中的指针:Unsafe类 [实战Java高并发程序设计 2]无锁的对象引用:AtomicReference [实战Java高并发程序设计 3]带有时间戳的对象 ...

  4. 【实战Java高并发程序设计 5】让普通变量也享受原子操作

    [实战Java高并发程序设计 1]Java中的指针:Unsafe类 [实战Java高并发程序设计 2]无锁的对象引用:AtomicReference [实战Java高并发程序设计 3]带有时间戳的对象 ...

  5. 【实战Java高并发程序设计 3】带有时间戳的对象引用:AtomicStampedReference

    [实战Java高并发程序设计 1]Java中的指针:Unsafe类 [实战Java高并发程序设计 2]无锁的对象引用:AtomicReference AtomicReference无法解决上述问题的根 ...

  6. 《实战Java高并发程序设计》读书笔记

    文章目录 第二章 Java并行程序基础 2.1 线程的基本操作 2.1.1 线程中断 2.1.2 等待(wait)和通知(notify) 2.1.3 等待线程结束(join)和谦让(yield) 2. ...

  7. 《实战java高并发程序设计》源码整理及读书笔记

    日常啰嗦 不要被标题吓到,虽然书籍是<实战java高并发程序设计>,但是这篇文章不会讲高并发.线程安全.锁啊这些比较恼人的知识点,甚至都不会谈相关的技术,只是写一写本人的一点读书感受,顺便 ...

  8. (转载)java高并发:CAS无锁原理及广泛应用

    java高并发:CAS无锁原理及广泛应用   版权声明:本文为博主原创文章,未经博主允许不得转载,转载请注明出处. 博主博客地址是 http://blog.csdn.net/liubenlong007 ...

  9. 【实战Java高并发程序设计 1】Java中的指针:Unsafe类

    是<实战Java高并发程序设计>第4章的几点. 如果你对技术有着不折不挠的追求,应该还会特别在意incrementAndGet() 方法中compareAndSet()的实现.现在,就让我 ...

  10. 《实战Java高并发程序设计》读书笔记三

    第三章 JDK并发包 1.同步控制 重入锁:重入锁使用java.util.concurrent.locks.ReentrantLock类来实现,这种锁可以反复使用所以叫重入锁. 重入锁和synchro ...

随机推荐

  1. linux ps命令,查看进程cpu和内存占用率排序(转)

    使用以下命令查看: ps -aux | sort -k4,4n ps auxw --sort=rss ps auxw --sort=%cpu linux 下的ps命令 %CPU 进程的cpu占用率 % ...

  2. Oracle over函数

    Oracle over函数   SQL code: sql over的作用及用法RANK ( ) OVER ( [query_partition_clause] order_by_clause )DE ...

  3. PostgreSQL中标准的SQL boolean数据类型

  4. sql基础大全

  5. hellocharts的折线图与柱状图的结合之ComboLineColumnChartView

    哼哼,网上找了半天都不全,所以决定自己写一个完整的可以直接贴代码的 test.xml <?xml version="1.0" encoding="utf-8&quo ...

  6. ip二进制计算,与运算算网段

    每8位二进制,各位从左到右对应的权值分别是 128,64,32,16, 8,4,2,1 .(即2的n-1次方,n是从右到左当前位的位数)  所以随便拿一个256以内的数给你化为二进制,都可以分解为权值 ...

  7. pwnable.kr的passcode

    前段时间找到一个练习pwn的网站,pwnable.kr 这里记录其中的passcode的做题过程,给自己加深印象. 废话不多说了,看一下题目, 看到题目,就ssh连接进去,就看到三个文件如下 看了一下 ...

  8. 疑难问题解决备忘录(3)——ubuntu12.04配置vsftp本地用户登录

    vsftpd.conf相关项设置 local_enabled = YES write_enable=YES pam_service_name=ftp pam_service_name按默认的vsftp ...

  9. SilverLight抛出 System.InvalidOperationException: 超出了2083 的最大URI

    在SilverLight中对于抛出 System.InvalidOperationException: 超出了 2083 的最大 URI 长度 的异常 处理 其实很简单 在 EntityFramewo ...

  10. 【JBOSS】控制台数据库连接信息

    数据库连接 信息 进入         在这个页面中(IP和端口已自己的为主):ConnectionCount 这个项目代表在服务开启后,总共使用的连接数!ConnectionCreatedCount ...