网站首页 > java教程 正文
AtomicReference和前面所讲的AtomicInteger非常的类似,只不过后者是对基本类型int的封装,底层采用的是Unsafe中的compareAndSwapInt来实现的原子操作,而这个方法是比较整型数值是否相等的。而对于这一篇文章的AtomicReference则是调用Unsafe中的compareAndSwapObject()方法来实现原子操作的,这个方法是比较两个对象的地址是否相等的。所以总结如下:
- AtomicInteger/AtomicBoolean/AtomicLong是对单一基本变量进行原子操作的,底层是通过比较数值是否相等来实现CAS操作的。
- AtomicIntegerArray/AtomicLongArray是对基本类型数组中的元素进行原子操作的,底层也是通过比较数值是否相等来实现CAS操作的,与数组本身没有关系
- AtomicIntegerFieldUpdater/AtomicLongFieldUpdater是对对象中的成员基本变量进行原子操作的,底层也是通过比较数值是否相等来实现CAS操作。
- AtomicReference是对一个对象地址进行原子操作的,底层是通过比较对象地址是否相同来实现CAS操作的。
- AtomicReferenceArray是对对象数组中的元素进行原子操作的,底层是通过比较元素的对象地址是否相同
- AtomicReferenceFieldUpdater是对对象中的成员变量进行原子操作的,底层是通过比较成员变量的地址是否相同。
前面3个我已经做了非常详细的说明,所以接下来就全面的分析后面3个是怎样实现的。
本篇文章的主要内容:
1:AtomicReference实例和源码分析 2:AtomicReferenceArray实例和源码分析 3:AtomicReferenceFieldUpdater原理分析
一、AtomicReference实例和源码分析
我们首先通过一个实例来看一下AtomicReference到底怎么使用。
public class AtomicReferenceTest { public static void main(String[] args) { AtomicReferenceObj obj1 = new AtomicReferenceObj(10); AtomicReferenceObj obj2 = new AtomicReferenceObj(20); AtomicReference<AtomicReferenceObj> ar = new AtomicReference<>(obj1); obj1.setAge(1); ar.compareAndSet(obj1, obj2); System.out.println("age=" + ar.get().getAge()); } static class AtomicReferenceObj { private Integer age; public AtomicReferenceObj(Integer age) { this.age = age; } //省略setter和getter方法 } }
上面这个实例做如下说明:
1:把对象obj1传递给了AtomicReference 2:将对象obj1中的成员变量age修改成1 3:通过CAS比较并交换数据,如果成功了,则变成obj2,如果失败还是obj1
运行结果如下:
从上面运行结果可以看出最终的结果变成了obj2的age=20,虽然在调用CAS方法之前修改了obj1中的age的值,但是并没有什么影响,这也说明了,AtomicReference底层的CAS是比较对象的地址的,与对象中的成员变量的值是否发生变化没有关系。
如果是对象地址发生了变化,那么结果又是怎样呢?将上面的例子稍微改变如下:
AtomicReferenceObj obj1 = new AtomicReferenceObj(10); AtomicReferenceObj obj2 = new AtomicReferenceObj(20); AtomicReferenceObj obj3 = new AtomicReferenceObj(30); AtomicReference<AtomicReferenceObj> ar = new AtomicReference<>(obj1); obj1 = obj3; ar.compareAndSet(obj1, obj2); System.out.println("age=" + ar.get().getAge());
对上面这个例子说明如下:
1:把obj1传递给了AtomicReference 2:把对象obj3赋值给了对象obj1 3:通过CAS比较并交换数据,如果成功了,则变成obj2,如果失败还是obj1
运行结果如下图:
这个例子的结果打印出了obj1中的age=10,说明第三步CAS操作并没有成功,因为在CAS操作之前,对象ob1变成了obj3,所以引用对象的地址变成了对象obj3,这样CAS就会失败。
通过这两个例子大家知道了AtomicReference在CAS操作时是比较的对象的地址是否发生了变化,与对象中的成员变量无关。
接下来我们从源码角度去分析它的原理所在。
第一步:还是先看看AtomicReference中的成员变量
//封装CAS的Unsafe类 private static final Unsafe unsafe = Unsafe.getUnsafe(); //对象地址在内存的偏移量 private static final long valueOffset; //原子操作的对象value,被volatile修饰 private volatile V value; //静态代码块获取对象地址的偏移量 static { try { valueOffset = unsafe.objectFieldOffset (AtomicReference.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } }
是不是和AtomicInteger中的成员变量类似,所以它的底层实现也是通过关键字volatile+CAS。
第二步:构造函数
//构造函数1: public AtomicReference(V initialValue) { value = initialValue; }
初始化value,因为value是volatile,所以对value的写,Java内存模型(JMM)会立刻把value刷新到主存中,会对其他线程可见。
构造函数2: public AtomicReference() { }
这个构造函数没有传递任何值给底层的value,所以value=null
第三步:重要的方法
public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); }
上面这个方法就是AtomicReference中的CAS方法,它直接调用了Unsafe中的方法,继续跟进。
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
它是一个本地方法,是通过比较对象var1的地址和对象var4的地址是否相同,如果相同,则替换成对象var5,如果不同,则更新失败。所以从源码角度可以看出,AtomicReference是比较的对象的地址是否相同,而AtomicInteger是比较的值是否相同。
通过上面的实例和源码分析后,AtomicReference也是非常的好理解的,接下来我们看看下面与AtomicReference相关的两个类又是怎么回事。
二、AtomicReferenceArray实例和源码分析
前面的文章我们介绍了AtomicIntegerArray,它是对数组中的元素进行原子操作的,与数组本身无关,AtomicReferenceArray是不是也是这个原理呢,答案是肯定的,接下来我们用例子和源码去证明我们的猜测。
public class AtomicReferenceArrayTest { public static void main(String[] args) { AtomicReferenceArray<User>arr = new AtomicReferenceArray<>(5); User user1 = new User(1); User user2 = new User(2); arr.set(0,user1); arr.compareAndSet(0,user1,user2); System.out.println("age="+arr.get(0).getAge()); } static class User{ private Integer age; public User(Integer age) { this.age = age; } //省略getter和setter方法 } }
运行结果:
从上面的例子中可以看出是对数组的元素进行的操作,所以从实例中也证明了我们猜想,那么从源码角度我们去看一下是否是这样的。
private static final Unsafe unsafe; //数组中第一个元素的偏移量 private static final int base; private static final int shift; //底层的数组 private final Object[] array;
上面的成员变量的这种定义是不是非常的熟悉,在讲解AtomicIntegerArray时就定义了类似的变量。那我们猜想必定有一个静态代码块来获取数组中元素的偏移量,我们继续往下看。
static { try { unsafe = Unsafe.getUnsafe(); arrayFieldOffset = unsafe.objectFieldOffset (AtomicReferenceArray.class.getDeclaredField("array")); base = unsafe.arrayBaseOffset(Object[].class); int scale = unsafe.arrayIndexScale(Object[].class); if ((scale & (scale - 1)) != 0) throw new Error("data type scale not a power of two"); shift = 31 - Integer.numberOfLeadingZeros(scale); } catch (Exception e) { throw new Error(e); } }
看到这是不是一目了然,那就明白了AtomicReferenceArray的原理和AtomicIntegerArray的原理是相同的,都是对数组中元素进行原子操作的,与数组本身并没有关系,如果不熟悉这个原理的,请仔细看一下AtomicIntegerArray这一篇文章。
三、AtomicReferenceFieldUpdater原理解析
在分析AtomicIntegerFieldUpdater的时候我们知道它有很多的限制,并且这个类不太常用,那AtomicReferenceFieldUpdater也有这样的限制吗?我们接下来继续分析。
通过查看源码,它的源码结构和AtomicIntegerFieldUpdater类似,也是一个抽象类,通过一个静态方法获取一个实例,如图。
通过上面几篇文章对Java并发包中atomic包主要讲解了两大类:
第一类:通过CAS比较值是否相等,相等则交换。
第二类:通过CAS比较对象地址是否相等,相等则交换。
仔细研究过CAS原理的都知道它有一个问题,就是ABA问题,所以接下来的文章看看JDK是怎样解决这个问题的。
猜你喜欢
- 2024-10-20 使用idea将Java项目打成Jar包,之后生成exe可执行文件
- 2024-10-20 Java并发包,你需要掌握的atomic(java并发实现原理)
- 2024-10-20 一款易于使用的Java验证码软件包(javaee验证码)
- 2024-10-20 Java 覆盖 jar 包内的方法(java覆盖第三方jar中类文件)
- 2024-10-20 都说Java程序占内存多,那么Java对象究竟占多少内存?
- 2024-10-20 JAVA学习笔记之导包(java中的导包命令是哪个)
- 2024-10-20 简单介绍Java 的JAR包、EAR包、WAR包区别
- 2024-10-20 Java juc包学习笔记(java的juc包)
- 2024-10-20 Java带包结构调用命令行运行编译(java怎么使用包)
- 2024-10-20 Java并发包-atomic包-让您彻底掌握AtomicInteger源码
你 发表评论:
欢迎- 07-21如何将 iPhone 中的联系人导出到 Excel/CSV?
- 07-21sql查询的字段数据中有逗号。放到csv文件会分开,如何解决?
- 07-21在 WebAPI 里生成 csv zip 文件(webapi怎么发布在iis上)
- 07-21如何把csv格式转换成Excel格式(csv格式怎么转换)
- 07-21如何将高程导出为XYZ或CSV高程点(如何将高程数据导入cad)
- 07-21使用python把csv汇总成excel(python怎么将csv文件中的列存入列表)
- 07-21解决PHP导出CSV文件中文乱码问题(php导出excel文件)
- 07-21使用vba将Excel 文件转成 CSV 文件
- 最近发表
-
- 如何将 iPhone 中的联系人导出到 Excel/CSV?
- sql查询的字段数据中有逗号。放到csv文件会分开,如何解决?
- 在 WebAPI 里生成 csv zip 文件(webapi怎么发布在iis上)
- 如何把csv格式转换成Excel格式(csv格式怎么转换)
- 如何将高程导出为XYZ或CSV高程点(如何将高程数据导入cad)
- 使用python把csv汇总成excel(python怎么将csv文件中的列存入列表)
- 解决PHP导出CSV文件中文乱码问题(php导出excel文件)
- 使用vba将Excel 文件转成 CSV 文件
- python爬虫25 | 爬取的数据怎么保存?CSV了解一下
- MySQL 导出数据(mysql 导出数据 判断成功)
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)