本篇文章不会教你怎么用AtomicIntegerFieldUpdater类,也不会给你讲解里面的机制,本篇只是最直观的提供一个demo,来让你直观的感知到原子更新类的效果
What is AtomicIntegerFieldUpdater
AtomicIntegerFieldUpdater就是用来更新某一个实例对象里面的int属性的。
但是注意,在用法上有规则:
- 字段必须是volatile类型的,在线程之间共享变量时保证立即可见
- 字段的描述类型(修饰符public/protected/default/private)是与调用者与操作对象字段的关系一致。也就是说调用者能够直接操作对象字段,那么就可以反射进行原子操作。
- 对于父类的字段,子类是不能直接操作的,尽管子类可以访问父类的字段。
- 只能是可修改变量,不能使final变量,因为final的语义就是不可修改。
- 只能是实例变量,不能是类变量,也就是说不能加static关键字。
- 对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。如果要修改包装类型就需要使用AtomicReferenceFieldUpdater。
具体一个demo来感受
简单说明该demo:
例子定义了一个int inet 的变量,通过简单的inet++ 运算来说明在高并发场景下,非原子更新会造成的后果。
为了方便你理解,这里简单讲解一下 inet++ 在Java中的底层原理:
我们知道,所有高级自然语言(java,c,c++),最终都会被编译成机器能识别的字节码(如果你不明白计什么是字节码,可以去百度,如果你是大学生,你只要学过《计算机组成原理》和《编译原理》,你就会明白大概是怎么一回事)
而inet++在底层中,通俗的讲,他的字节码产生的作用顺序是这样的:
- 取出变量inet中的值保存到某个地方
- 然后从某个地方取出这个值,进行+1运算
- 运算完成后再赋值回inet
(我们可以看出,inet++在Java的底层,是如何工作的)
(这种例子在数据库事务,锁的知识体系里面,经常可见,比如银行的钱包转账问题,就需要完成原子性操作)
(通过上述的解释,我们知道在Java中对int类型的变量进行++操作,是拆分为几个步骤完成的,它是非原子更新的,所以会产生并发问题)
在高并发场景下,就会出现这样的问题
类似的场景:订票系统,钱包扣费系统,计数系统等
目的:有N个线程操作inet++,inet的结果就应该为N
并发问题出现后:inet的结果会 < N
(如下图:2个线程在并发场景下,执行inet++的操作。但是由于没有加锁,inet++不满足CAS原子更新,因此就会出现结果不为3的情况。预期结果,1+2=3。 但是由于出现了并发问题,inet最终是2)

下面贴出代码
通过两个方法的结果来直观感受
一个是使用了AtomicIntegerFieldUpdater类来达到整形变量CAS原子更新。
另外一个方法为没有任何并发控制的普通代码
public class AtomIntegerFieldUpdateDemo {
//控制并发数量
final static int THREAD_NUM = 10000;
/*
* main方法
*/
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("----------- start testWithAtomic() --------------");
try {
AtomIntegerFieldUpdateDemo.testWithAtmoic();
} catch (InterruptedException e) {
System.out.println("---------- Error: " + e.getCause().getMessage());
}
}
});
t1.start();
t1.join();
System.out.println("\n=========== 结束testWithAtomic ===============");
System.out.println("\n ------------------ start testWithoutAtomic() ----------------------");
testWithoutAtmoic();
System.out.println(" ################## End of this demo ################# ");
}
/**
* 使用了AtomicIntegerFieldUpdater类控制int变量原子更新的方法
*/
public static void testWithAtmoic () throws InterruptedException {
AtomicIntegerFieldUpdater<DemoBean> atom = AtomicIntegerFieldUpdater.newUpdater(DemoBean.class, "inte");
DemoBean bean = new DemoBean();
CountDownLatch latch = new CountDownLatch(THREAD_NUM);
final boolean[] isOk = {false};
for (int i = 0; i < THREAD_NUM; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
SecureRandom secureRandom = new SecureRandom();
Thread.sleep(secureRandom.nextInt(10) * 10L /* 0~9随机数 乘以100ms */);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (atom.getAndIncrement(bean) == THREAD_NUM-1) {
isOk[0] = true;
}
latch.countDown();
}
}).start();
}
latch.await();
System.out.println("高并发结果:" + isOk[0] + "\n"
+ "inte: " + bean.inte);
}
/**
* 无使用原子更新,无法解决高并发场景下,原子问题。
* 最终结果isOk大概率是false,inte的结果大概率小于THREAD_NUM
*
* 出现并发的原因主要是“原子性"没有满足
* 高并发下,bean.inet++(非原子性)操作,可能会在某一时刻,2个线程都拿到inet=1,然后对1进行++,得到2.
* 这样本来2个线程的结果inet应该是加2次,变为3 。但是这里却只得到2
*
* 因此可以通过jdk1.5并发编程提供的工具类实现int的原子操作。或者自己实现原子性(如使用锁)
*/
public static void testWithoutAtmoic() throws InterruptedException {
DemoBean bean = new DemoBean();
CountDownLatch latch = new CountDownLatch(THREAD_NUM);
final boolean[] isOk = {false};
for (int i = 0; i < THREAD_NUM; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
SecureRandom secureRandom = new SecureRandom();
Thread.sleep(secureRandom.nextInt(10) * 10L /* 0~9随机数 乘以100ms */);
} catch (InterruptedException e) {
e.printStackTrace();
}
bean.inte++; //此处在高并发场景下,会出现并发问题
if (bean.inte == THREAD_NUM) {
isOk[0] = true;
}
latch.countDown();
}
}).start();
}
latch.await();
System.out.println("高并发结果:" + isOk[0] + "\n"
+ "inte: " + bean.inte);
}
}
class DemoBean {
volatile int inte;
public DemoBean() {
this.inte = 0;
}
}
运行结果
可以看出,10000个线程在高并发场景下执行inet++的操作。
- 在使用了AtomicIntegerFieldUpdater原子更新后,inte最终结果为10000没有错。
- 而第二个方法,没有使用任何技术实现原子更新,可以看出inte的结果小于10000