写在前面
本文一起来看下使用JUC包的AtomicXxxxFieldUpdater实现更新的原子性。代码位置如下:
当前有针对int,long,ref三种类型的支持。如果你需要其他类型的支持的话,也可以照葫芦画瓢。
1:例子
1.1:普通方式
程序:
package x;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class NormalUpdaterTest {
CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) throws InterruptedException {
Account account = new Account(0);
List<Thread> list = new ArrayList<>();
for (int i = 0; i < 20; i++) {
Thread t = new Thread(new Task(account));
list.add(t);
t.start();
}
for (Thread t : list) {
t.join();
}
System.out.println(account.toString());
}
private static class Task implements Runnable {
private Account account;
Task(Account account) {
this.account = account;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.increMoney();
}
}
}
static class Account {
private volatile int money;
public Account(int initial) {
this.money = initial;
}
public void increMoney() {
money++;
}
public int getMoney() {
return money;
}
@Override
public String toString() {
return "Account{" +
"money=" + money +
'}';
}
}
}
我们期望的结果是100,但结果往往是比100小的,因为整个操作过程并不是线程安全的,根本原因是这个i++它不是原子的,而是三步走,首先拿到i,接着对i+1,最后将i+1的结果写回内存,所以大概率存在盖结果
情况发生,运行如下:
Account{money=91}
Process finished with exit code 0
当然这个结果并不是固定不变,因为存在偶然性,但一般都是小于100的。
1.2:原子方式
package x;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class FieldlUpdaterTest {
CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) throws InterruptedException {
Account account = new Account(0);
List<Thread> list = new ArrayList<>();
for (int i = 0; i < 20; i++) {
Thread t = new Thread(new Task(account));
list.add(t);
t.start();
}
for (Thread t : list) {
t.join();
}
System.out.println(account.toString());
}
private static class Task implements Runnable {
private Account account;
Task(Account account) {
this.account = account;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.increMoney();
}
}
}
static class Account {
private volatile int money;
private AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Account.class, "money");
public Account(int initial) {
this.money = initial;
}
public void increMoney() {
// money++;
// fieldUpdater.incrementAndGet(money);
// 以原子的方式更新this的money
fieldUpdater.incrementAndGet(this);
}
public int getMoney() {
return money;
}
@Override
public String toString() {
return "Account{" +
"money=" + money +
'}';
}
}
}
相比于普通方式增加了AtomicIntegerFieldUpdater fieldUpdater
专门用来负责更新,更新逻辑也对应的变为fieldUpdater.incrementAndGet(this);
,这个时候再运行:
Account{money=100}
Process finished with exit code 0
就对了,因为此时加了cas的乐观锁。
2:源码分析
首先看下代码AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Account.class, "money");
:
AtomicIntegerFieldUpdaterImpl(final Class<T> tclass,
final String fieldName,
final Class<?> caller) {
final Field field;
final int modifiers;
try {
...
} catch (Exception ex) {
throw new RuntimeException(ex);
}
...
// 获取要更新的类和字段的内存偏移量
this.cclass = (Modifier.isProtected(modifiers) &&
tclass.isAssignableFrom(caller) &&
!isSamePackage(tclass, caller))
? caller : tclass;
this.tclass = tclass;
this.offset = U.objectFieldOffset(field);
}
代码fieldUpdater.incrementAndGet(this);
:
// java.util.concurrent.atomic.AtomicIntegerFieldUpdater.AtomicIntegerFieldUpdaterImpl#getAndAdd
public final int getAndAdd(T obj, int delta) {
accessCheck(obj);
// 基于偏移量完成更新,其中getAndAddInt是cas的
return U.getAndAddInt(obj, offset, delta);
}
// sun.misc.Unsafe#getAndAddIntsun.misc.Unsafe#getAndAddInt
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
注意代码} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
当compare不成功的时候就会返回false,会尝试再次做+1操作,即自旋,直到成功。
3:相比于AtomicXxx的优势
更小的内存占用,为什么呢?比如如下代码:
class Account {
private volatile int money;
}
如果改造为AtomicInteger的方式则为;
class Account {
private volatile AtomicInteger money = new AtomicInteger(0);
}
AtomicInteger money的大小在不考虑padding的情况下大概为16字节对象头+4字节的对象内容
,即20个字节,那么创建100个对象大小就是2000个字节,但如果是使用AtomicXxxFieldUpdater的代码就如下:
private volatile int money;
private static AtomicIntegerFieldUpdater fieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Account.class, "money");
}
这里我们简单的假定AtomicIntegerFieldUpdater fieldUpdater的大小是24个字节,int money
的大小是4个字节。此时我们创建100个对象,money和fieldUpdater占用的大小就是4*100+24
,即424,因为fieldUpdater是静态的,所以只有独一份。
可以看到AtomicIntegerFieldUpdater的方式占用了比AtomicInteger少得多的内存,而占用更少的内存,也意味着你的程序不需要申请更多的内存,所以程序执行的速度也会更快。
写在后面
参考文章列表
jvm之对象大小分析。
多线程之JUC 。