前言
最近在看资料的时候偶然间看到了AtomicLongFieldUpdater这个工具类,觉得新鲜就查阅了相关的资料,发现居然是jdk1.5就有的工具类,不禁感叹自己对Java的理解还是太浅了,于是在此整理一下该类的资料,作为知识储备。本篇博客原文地址AtomicXFieldUpdater,属性原子修改的外部工具类
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,有几个需要关注的点
1. updater通过AtomicLongFieldUpdater.newUpdater
来构造,通过传入类对应的class和要修改的属性名”id”指定该updater所要修改的类的属性,此处为Person的id
2. 通过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");
Node getLeft() { 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