内容简介:AtomicXFieldUpdater,属性原子修改的外部工具类
最近在看资料的时候偶然间看到了AtomicLongFieldUpdater这个 工具 类,觉得新鲜就查阅了相关的资料,发现居然是jdk1.5就有的工具类,不禁感叹自己对 Java 的理解还是太浅了,于是在此整理一下该类的资料,作为知识储备。
AtomicXFieldUpdater
根据名字,我们可以知道 AtomicLongFieldUpdater
是对long型field进行原子update的,它是一个工具类,那么有long型的话是不是就有其他类型?通过查看相应的包,发现还有 AtomicIntegerFieldUpdater
和 AtomicReferenceFieldUpdater
两个“同胞兄弟”,它们3个加起来,支持了对Integer、long、Reference的原子操作,分别对应的原子类为AtomicInteger、AtomicLong、AtomicReference,那么updater和对应的原子类有什么区别呢?
拿 AtomicLongFieldUpdater
来说,通过API文档,其中写了一句:“通过反射技术来对volatile修饰的long型属性进行原子更新”。这里面有几个关键词: 反射
、 volatile
、 原子更新
。
本着 talk is cheap, show me the code
的原则,直接上代码来展示它的用法,代码来自 博客3
// LongTest.java的源码 import java.util.concurrent.atomic.AtomicLongFieldUpdater; public class LongFieldTest{ public static void main(String[] args){ // 获取Person的class对象 Class cls = Person.class; // 新建AtomicLongFieldUpdater对象,传递参数是“class对象”和要update的属性名"id" AtomicLongFieldUpdater mAtoLong = AtomicLongFieldUpdater.newUpdater(cls, "id"); Person person = new Person(12345678L); // 比较person的"id"属性,如果id的值为12345678L,则设置为1000。 mAtoLong.compareAndSet(person, 12345678L, 1000); System.out.println("id="+person.getId()); } } class Person{ volatile long id; public Person(long id){ this.id = id; } public void setId(long id){ this.id = id; } public long getId(){ return id; } }
上面代码最终输出的为1000,有几个需要关注的点
-
updater通过
AtomicLongFieldUpdater.newUpdater
来构造,通过传入类对应的class和要修改的属性名”id”指定该updater所要修改的类的属性,此处为Person的id - 通过compareAndSet(CAS)来进行原子的修改,12345678L是expectedValue,如果传入的person对象的id不为12345678L,则修改失败,这里保证了线程安全,试想在多线程环境下,threadA和threadB同时执行CAS操作,12345678L是传入的expectedValue,只有第一个到达的线程可以成功的执行CAS,将该id修改成希望修改的值
根据Java API的说法,这个工具类不能保证完全的原子性,它只能保证相同updater上执行CAS和set操作的原子性,因此它的原子性是弱与AtomicLong的。为什么呢?因为AtomicLong是对long型属性加了一层原子引用,任何想要修改该long值的操作都需要先获得该原子引用,而updater不会为属性增加原子引用,它是通过反射技术,通过外部操作去修改long型属性值,因此它的原子保证也是通过外部限制的,因此只能保证同一updater进行CAS和set的原子性。
可以说上面的特性既是 AtomicLongFieldUpdater
的优点,也是它的缺点,为什么说呢?试想对于下面的类(代码来源于 博客1
),Record是一个记录类,保存了系统中的一条记录信息,version属性是版本的计数。
public class Record{ private final AtomicLong version = new AtomicLong(0); public long update(){ return version.incrementAndGet(); } }
update方法更新版本号使其加一。这个类看起来逻辑十分清晰,但有一个隐藏的缺陷,就是每个Record都有一个对应的AtomicLong与之对应,如果系统有上亿条Record( 夸张而谈 ),而我们对于update方法的调用并不是很多,更多的是读取version的值,那么这会对堆空间造成严重的污染,大量的AtomicLong存在于堆上( 栈上存引用,堆上存实际的对象值 ),造成了内存的浪费,且AtomicLong的读取需要使用get方法,效率比正常变量的读取要慢,那么如何优化呢?
使用 AtomicLongFieldUpdater
,我们可以把Record类定义如下:
public class Record{ private static final AtomicLongFieldUpdater<Record> VERSION = AtomicLongFieldUpdater.newUpdater(Record.class, "version"); private volatile long version = 0; public long update(){ return VERSION.incrementAndGet(this); } }
通过改造,对于version属性正常的读取操作可以像普通属性读取那样进行,而当需要update的时候,使用 AtomicLongFieldUpdater
的 incrementAndGet
方法来进行,这样内存空间节省了下来(1. AtomicLong对象的引用和值统统不需要,对于拥有大量Record的系统无疑是很大的内存空间 2. version的读取和普通属性读取相同),还有一个好处是 AtomicLongFieldUpdater
是一个静态常量,它在Record类加载的时候就放在了堆空间的常量池中,对于N个Record,只需要一个 AtomicLongFieldUpdater
即可(类静态常量),如何还不清晰的话,可以把static final修饰的看作一个”全局常量”,整个系统只存在一个。
因此,虽然原子性不如 AtomicLong
,但它的效率很高,在特定的场景下有着很好的应用。Stack Overflow有一个回答比较好 引文3
,对于CPU的消耗来说,其从小到大依次为:
-
long
: 最小, 多线程不安全 -
volatile long
: 消耗>long, 多线程读取安全,但无法进行原子操作 -
AtomicLong
:消耗>volatile long,多线程读安全,可进行原子操作 -
AtomicLongFieldUpdate
:消耗>AtomicLong,因为其使用反射技术,多线程安全和可原子操作
因此 AtomicLongFieldUpdate
的场景是,对于long的正常读写是占比很大的操作,原子操作只占很小的比例,并且多线程读取时必要的,此时可以使用 AtomicLongFieldUpdate
+ volatile
的组合。
AtomicReference
如果我们有一个双向链表,想要对链表中节点的替换进行原子操作,此时我们可以使用如下的代码:
class Node{ private volatile Node left, right; private static final AtomicReferenceFieldUpdater<Node, Node> leftUpdater = AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "left"); private static AtomicReferenceFieldUpdater<Node, Node> rightUpdater = AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "right"); NodegetLeft(){ return left; } boolean compareAndSetLeft(Node expect, Node update){ return leftUpdater.compareAndSet(this, expect, update); } // ... and so on }
compareAndSetLeft
方法原子的将left指针从expect修改为update。我们知道,通过CAS实现的并发链表,其并发访问性能是最好的,因为无需给节点进行加锁,上面的代码是一个多线程访问的双向链表,拥有良好的读性能。
总结
通过上文的分析,总结 AtomicLongFieldUpdate
的使用场景主要如下(对另外两个也适用):
- 多数情况下对属性操作为正常的读写,偶尔需要原子操作(CAS)
- 该对象在系统中大量存在(如Record),需要节省AtomicLong的内置原子引用所带来的内存消耗.
据说ConcurrentHashMap, ConcurrentLinkedQueue和ConcurrentSkipListMap中有对AtomicXFieldUpdater工具类的使用,有机会可以通过源码加深一下理解,也可以读一下AtomicXFieldUpdater类的源码,对于AtomicLong和AtomicLongFieldUpdate的内存空间比较可以参考 引文6
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Ruby Programming Language
David Flanagan、Yukihiro Matsumoto / O'Reilly Media, Inc. / 2008 / USD 39.99
Ruby has gained some attention through the popular Ruby on Rails web development framework, but the language alone is worthy of more consideration -- a lot more. This book offers a definition explanat......一起来看看 《The Ruby Programming Language》 这本书的介绍吧!