【实战Java高并发程序设计 2】无锁的对象引用:AtomicReference
来源:互联网 发布:mac下载速度超级慢 编辑:程序博客网 时间:2024/05/21 17:33
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
- 【实战Java高并发程序设计 2】无锁的对象引用:AtomicReference
- 无锁的对象引用:AtomicReference(转)
- 【实战Java高并发程序设计 3】带有时间戳的对象引用:AtomicStampedReference
- 【实战Java高并发程序设计6】挑战无锁算法
- AtomicReference(无锁的对象引用)与AtomicStampedReference与(带有时间戳的对象引用)
- 实战Java高并发程序设计-04 Java并发包锁和其他工具的使用
- 《Java高并发程序设计》学习 --5.4 高性能的生产者-消费者:无锁的实现
- Java高并发程序设计笔记4之无锁
- 《Java高并发程序设计》学习 --4.4 无锁
- 实战Java高并发程序设计之概念
- 实战java高并发程序设计第一章读书笔记
- 实战Java高并发程序设计之Semaphoer
- 实战Java高并发程序设计之ReadWriteLock
- 实战Java高并发程序设计之CountDownLatch
- 实战Java高并发程序设计之CyclicBarrier
- 实战Java高并发程序设计之LockSupport
- 实战java高并发程序设计读书笔记一
- Button的圆角边框样式
- 机器学习(一) - - 基本术语
- 关于jquery easy-ui 中radio选中问题
- iOS中手势之拖动手势UIPanGestureRecognizer
- C++高级篇(2)——运算符重载及流类库
- 【实战Java高并发程序设计 2】无锁的对象引用:AtomicReference
- Android进程间通信的几种方式
- 四元数仰角和俯角的转换(第一人称视角)
- ibatis的Iterate元素的用法。
- centos7 安装 docker1.12
- ORA-19815: WARNING: db_recovery_file_dest_size is 100.00% 处理方法
- 使用 Vuex + Vue.js 构建单页应用
- 向powerpc上移植dhcp
- ubuntu dash shell 改为 bash