博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
并发之无锁技术归纳
阅读量:6183 次
发布时间:2019-06-21

本文共 8440 字,大约阅读时间需要 28 分钟。

并发之AtomicBoolean/AtomicBooleanArray/AtomicBooleanUpdateFeild
1 和前面的AtomicInteger很相似或者原理基本一致的;原理就是使用了CAS算法实行循环重试的方式来保证一组操作是原子性的操作;
2 同样的也是一个无锁技术的应用;
3 在源码内部,使用1表示true,使用0表示false;
同样的boolean类型的变量在并发情况下也是不安全的,因此使用了AtomicBoolean来保障原子性的操作;
AtomicReference案例:
和AtomicInteger、AtomicLong原大同小异;只是AtomicLong而言,32位的数分为高16位和低16位分别并行计算;
AtomicReference是一种模板类,可以用来封装任何类型的数据;
public class AtomicReference
implements java.io.Serializable

 

 
package automic;import java.util.concurrent.atomic.AtomicReference;public class AtomicRefrenceTest {     public final static AtomicReference
atomicString = new AtomicReference
("gosaint"); /** * 创建了10个线程,同时去修改atomicString的值,但是在并发状态下只有一个可以修改成功! * @param args */ public static void main(String[] args) { // 开启10个线程 for (int i = 0; i < 10; i++) { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(Math.abs((int) Math.random() * 100)); } catch (Exception e) { e.printStackTrace(); } if (atomicString.compareAndSet("gosaint", "mrc")) { System.out.println(Thread.currentThread().getId() + "Change value"); } else { System.out.println(Thread.currentThread().getId() + "Failed"); } } }).start(); } }}

 

看运行结果:只有一个线程对值进行了修改;
10Change value9Failed11Failed13Failed14Failed15Failed12Failed16Failed18Failed17Failed

 

CAS的缺点:
    其实这个之前说过,为了深刻理解,这里再说明一下;比如在内存的初始值是3;现在存在两个线程去修改这个值;根据JVM内存模型;每一个线程都存在这个变量的副本;假设线程1去修改这个值;发现内存中的这个值是3;想要修改为4;但是此时线程2也去修改了内存的值;并且鲜牛该为2,再修改为3,此时线程1发现还是内存还是3,进行了修改;我们看到,虽然对于线程1来说,修改完成了,但是内存中的3确是经过修改2修改过的,这个值得状态已经发生了变化;如果我们对于某个值的状态很关注并且这个状态很重要;那么这个漏洞必须要避免;这也就是传说中的“ABA”问题;在JAVA中,通常是对内存中值得每一次修改添加一个时间戳作为版本号,再比较值的时候同样的时间戳的版本号也要比较;
在AtomicStampedReference类中,存在一个内部类Pair来封装值和时间戳;
private static class Pair
{ final T reference; final int stamp; private Pair(T reference, int stamp) { this.reference = reference; this.stamp = stamp; } static
Pair
of(T reference, int stamp) { return new Pair
(reference, stamp); } } public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair
current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); }

 

 
上面的源码在比较值得时候时间戳的信息同样的也进行比较;
package automic;import java.util.concurrent.atomic.AtomicStampedReference;public class AtomicTest {     /**      * AtomicStampedReference(V initialRef, int initialStamp)      *   创建具有给定初始值的新 AtomicStampedReference。      * @param args      */     //创建AtomicStampedReference,用户的账户是19,版本号是0     static AtomicStampedReference
asr=new AtomicStampedReference
(19,0); public static void main(String[] args) { //创建3个线程给用户充话费 for(int i=0;i<3;i++){ final int expectedStamp = asr.getStamp();//获取时间戳 new Thread(new Runnable() { @Override public void run() { while(true){ while(true){ //获取值 Integer expectedReference = asr.getReference(); /** * 小于20元话费,那么充值20 */ if(expectedReference<20){ if(asr.compareAndSet(expectedReference, expectedReference+20, expectedStamp, expectedStamp+1)){ System.out.println("充值成功,余额为:"+asr.getReference()); break; } }else{ break; } } } } }).start(); } //启动100个线程消费 new Thread(new Runnable() { @Override public void run() { for(int i=0;i<100;i++){ while(true){ int stamp = asr.getStamp(); Integer reference = asr.getReference(); if(reference>10){ if(asr.compareAndSet(reference, reference-10, stamp, stamp+1)){ System.out.println("消费10元,余额:"+ asr.getReference()); break; } }else{ break; } } try{ Thread.sleep(100); }catch(Exception e){ } } } }).start(); }}

 

解释下代码,有3个线程在给用户充值,当用户余额少于20时,就给用户充值20元。有100个线程在消费,每次消费10元。用户初始有9元,当使用AtomicStampedReference来实现时,只会给用户充值一次,因为每次操作使得时间戳+1。运行结果:
充值成功,余额为:39消费10元,余额:29消费10元,余额:19消费10元,余额:9

  

代码修改:修改为AtomicReference类
package automic;import java.util.concurrent.atomic.AtomicReference;import java.util.concurrent.atomic.AtomicStampedReference;public class AtomicTest2 {     /**      * AtomicStampedReference(V initialRef, int initialStamp)      *   创建具有给定初始值的新 AtomicStampedReference。      * @param args      */     //创建AtomicStampedReference,用户的账户是19,版本号是0     static AtomicReference
asr=new AtomicReference
(19); public static void main(String[] args) { //创建3个线程给用户充话费 for(int i=0;i<3;i++){ new Thread(new Runnable() { @Override public void run() { while(true){ while(true){ //获取值 Integer expectedReference = asr.get(); /** * 小于20元话费,那么充值20 */ if(expectedReference<20){ if(asr.compareAndSet(expectedReference, expectedReference+20)){ System.out.println("充值成功,余额为:"+asr.get()); break; } }else{ break; } } } } }).start(); } //启动100个线程消费 new Thread(new Runnable() { @Override public void run() { for(int i=0;i<100;i++){ while(true){ Integer reference = asr.get(); if(reference>10){ if(asr.compareAndSet(reference, reference-10)){ System.out.println("消费10元,余额:"+ asr.get()); break; } }else{ break; } } try{ Thread.sleep(100); }catch(Exception e){ } } } }).start(); }}

 

 
运行结果:出现了多次充值的现象:
充值成功,余额为:39消费10元,余额:29消费10元,余额:19充值成功,余额为:39消费10元,余额:29消费10元,余额:19充值成功,余额为:39消费10元,余额:29充值成功,余额为:39消费10元,余额:39消费10元,余额:29充值成功,余额为:39消费10元,余额:39

 

 
 
 
 
 
 
 
 
 
 
 
 

转载于:https://www.cnblogs.com/gosaint/p/9068195.html

你可能感兴趣的文章
Linux服务器配置——SAMBA
查看>>
我的WP7应用
查看>>
js打开连接 _无需整理
查看>>
我的友情链接
查看>>
C语言结合windowsApi遍历文件
查看>>
linux 系统无法启动的基本解决方法
查看>>
Yii框架学习笔记 [PHP]
查看>>
饿了么MySQL异地多活的数据双向复制经验谈
查看>>
MySQL的btree索引和hash索引的区别
查看>>
计算机基础
查看>>
我的友情链接
查看>>
Hystrix系列-4-Hystrix的动态配置
查看>>
oracle数字函数
查看>>
myeclipse svn 分支
查看>>
ORACLE CHAR,VARCHAR,VARCHAR2,NVARCHAR类型的区别与使用
查看>>
SQL Server AlwaysOn架构及原理
查看>>
spring-session学习
查看>>
PHP中类的使用,面向对象的思路
查看>>
istio 0.8 安装步骤
查看>>
Linux /Var/log 日志文件详解
查看>>