AtomicInteger类是jdk并发包下的一个原子操作类,顾名思义原子操作类就是实现变量的原子操作的,要深刻理解这个类的作用我们要先从i++这个操作说起。
情景假设:商城后台需要录入库存,现在商品A剩下0个库存,仓库管理员A点击添加按钮添加一个库存,仓库管理员B点击添加按钮添加一个库存。代码如下:
public class Product{
private int i;
Product(){
}
public void increament(){
i++;
System.out.println(i);
}
}
很简单的一个类,下面我们写一个线程类。
class IncreamentThread implements Runnable{
Product product;
IncreamentThread(Product p){
this.product = p;
}
@Override
public void run() {
this.product.increament();
}
}
也很简单,run方法里只有一句代码。再写一个简单的测试代码
public static void main(String[] args) {
Product t = new Product();
Thread t1 = new Thread(new IncreamentThread(t));//模拟管理员A
Thread t2 = new Thread(new IncreamentThread(t));//模拟管理员B
t1.start();
t2.start();
}
以上代码多运行几次你会发现打印的结果是:1和1 。正常来说两个管理员都点击了添加那么最终结果应该是2才对,是吧。我们知道i++这个操作其实不是原子操作。它分成了3步,第一步先取出i的值,第二步进行++操作,第三步将值放回内存。那么为什么会出现1和1呢?首先管理员A 取出i的值是0,同时管理员B取出i的值也是0,分别做++操作,分别放回内存,那么就会出现1和1的情况。对于打印的结果还有1和2、2和2,这两个结果都是正确的,只是打印的时候的问题,比如第一个线程++之后是1但是还没来得及打印就被线程2改变了值,变成了2所以就会出现2和2。
那么原子操作是用来干嘛的?就是用来防止1和1这种情况出现,因为我们需要它最终的结果是2,也就是说第二个操作库存添加动作必须建立在第一个操作的结果之上才行,对吧,我们稍微改一下程序:
public class Product{
private AtomicInteger i = new AtomicInteger(0);
Product(){
}
public void increament(){
System.out.println(i.incrementAndGet());
}
}
通过AtomicInteger 类我们就可以实现原子操作了,现在再运行程序多少次都不会出现1和1的情况了,因为调用incrementAndGet方法的时候是一个原子操作,即之前说的i++的三步操作这个方法是一步完成的。如果你跟进源码你会发现这个类并没有在哪个地方显式地使用同步,因为这个是通过底层硬件的支持来完成的。
原子操作类是用于对一个变量完成原子的操作,并不能替代锁。比如商品的最大库存为100个,但是使用原子操作类并不能控制这个100个库存的同步。它依然需要依赖synchronized或者高级锁来控制。