从这一篇文章开始,我开始对并发包的atomic包进行源码解析,首先通过下面的图了解以下atomic包中的类都是有哪些?
这一篇文章首先对AtomicInteger进行讲解。中我对并发包保证共享变量 线程安全 的机制做了讲解,如果对关键字volatile和CAS机制真正了解的话,AtomicInteger的内部原理是非常的容易理解的,那就跟着我的思路来打开AtomicInteger的大门吧。
本篇文章的主要内容:
1:AtomicInteger的实例 2:AtomicInteger的源码解析
1、AtomicInteger的实例
我首先利用一个简单的例子引入今天的话题:
public class AtomicIntegerTest { private static AtomicInteger ai = new AtomicInteger(0); private static int a = 0; public static void main(String[] args) throws InterruptedException { ordinaryIntegerTest(); atomicIntegerTest(); Thread.sleep(2000); System.out.println("a="+a); System.out.println("ai="+ai.get()); } //普通变量多线程下累加 public static void ordinaryIntegerTest(){ for(int i = 0;i<10;i++){ new Thread(()->{ for(int j = 0;j<10000;j++){ a++; } }).start(); } } //利用AtomicInteger多线程下累加 public static void atomicIntegerTest(){ for(int i = 0;i<10;i++){ new Thread(()->{ for(int j = 0;j<10000;j++){ ai.incrementAndGet(); } }).start(); } } }
通过上面的例子,大家能够猜出答案吗?运行结果如下:
通过上面两个运行结果可以看出:普通的变量每一次运行结果都不相同,而AtomicInteger变量每一次运行结果都是相同的,而且结果是正确的,所以在 多线程 下,普通变量无法保证线程安全,而AtomicInteger则在多线程下每一次结果都是正确的。那么我们知道了在多线程下AtomicInteger是线程安全的,它内部怎样实现的呢?我们接下来进入AtomicInteger源码一探究竟。
2、AtomicInteger的源码解析【Java自学网分析】
2.1、首先看一下AtomicInteger的定义
public class AtomicInteger extends Number implements java.io.Serializable {
//代码省略
}
//Number类的定义:主要将当前类型转化为基本类型
public abstract class Number implements java.io.Serializable {
//将当前类型转化成int
public abstract int intValue();
//将当前类型转化为long型
public abstract long longValue();
//将当前类型转化为float类型
public abstract float floatValue();
//将当前类型转化为double类型
public abstract double doubleValue();
//将当前类型转化为byte类型
public byte byteValue() {
return (byte)intValue();
}
//将当前类型转为short类型
public short shortValue() {
return (short)intValue();
}
}
2.2、AtomicInteger的成员变量
我说过,在并发包中大部分都是利用关键字 volatile 和CAS机制进行保证并发安全的,所以大家应该可以才想到在AtomicInteger成员变量中必定会出现被关键字volatile修饰的变量和 原子 封装类Unsafe,接下来开始证明我们的猜想。
//unsafe:表示对 原子操作 的封装类
private static final Unsafe unsafe = Unsafe.getUnsafe();
//被volatile修饰的变量
private volatile long value;
看到上面的两个成员变量,大家是不是找到了一点感觉,原来AtomicInteger是通过关键字volatile和CAS保证多线程下安全的修改一个共享变量。接下来我们继续往下分析。
//值value在内存的偏移地址 private static final long valueOffset; //静态代码块就是获取这个偏移地址的。 static { try { valueOffset = unsafe.objectFieldOffset (AtomicLong.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } }
上面的valueOffset就是变量value在内存的地址偏移量,也就是上一篇我介绍的CAS(V,E,N)中的V。
在AtomicInteger中还有一个成员变量,如下:
//表示当前虚拟机是否支持long类型无锁化CAS机制 static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8(); private static native boolean VMSupportsCS8();
上面把AtomicInteger的成员变量都介绍完了,通过被volatile修饰的value和unsafe封装的原子操作通过无锁化来保证对一个变量的线程安全,我们并没有看到加锁,说明AtomicInteger是一种乐观锁。
2.3、AtomicInteger的 构造函数
//第一个构造函数:无参数构造 public AtomicLong() { } //第二个构造函数:传递一个初始化value值。 public AtomicLong(long initialValue) { value = initialValue; }
构造函数非常的简单,我们接下来继续分析它的一些重要的方法。
2.4、AtomicInteger的重要方法
AtomicInteger类中主要分为四类方法,分别如下:
第一类:对单一value的操作。 第二类:对value的复合操作 第三类:对value更加复杂的操作。 第四类:对Number方法的实现。
第一类:对value的简单操作
//对volatile变量value的写 public final void set(long newValue) { value = newValue; } //对volatile变量value的读 public final long get() { return value; }
上面两个方法是对voaltile变量的写和读,大家还记得volatile的两个特性吗?
1:任意对volatile变量的写,JMM都会立刻将工作内存中的值刷新到主存中。 2:任意对volatile变量的读,JMM都会从主存中复制一份到自己的工作内存。
上面的两个方法是对被volatile修饰的单一变量的写和读,并没有任何的复合操作,所以它具有 可见性 、 有序性 和 原子性 ,所以在多线程下是安全的。
第二类:对value的符合操作
上一篇文章我讲过关键字volatile只能保证可见性和有序性(禁止指令重排序),但是volatile不具备原子性的特点,所以对value的复合操作不能保证线程安全,所以需要在配合CAS原子操作才能保证多线程下对value修改的安全。
//这个方法就是我们所说的CAS public final boolean compareAndSet(long expect, long update) { return unsafe.compareAndSwapLong(this, valueOffset, expect, update); } //通过CAS原子的设置value=newValue,然后返回旧值 public final int getAndSet(int newValue) { return unsafe.getAndSetInt(this, valueOffset, newValue); } //通过CAS原子的设置value+1,然后返回+1前的旧值 public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } //通过CAS原子的设置value+delta,然后返回+delta前的旧值 public final int getAndAdd(int delta) { return unsafe.getAndAddInt(this, valueOffset, delta); } //通过CAS原子的设置value+1,然后返回+1后的新值 public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; } //通过CAS原子的设置value+delta,然后返回+delta后的新值 public final int addAndGet(int delta) { return unsafe.getAndAddInt(this, valueOffset, delta) + delta; } //通过CAS原子的设置value-1,然后返回-1前的旧值 public final int getAndDecrement() { return unsafe.getAndAddInt(this, valueOffset, -1); } //通过CAS原子的设置value-1,然后返回-1后的新值 public final int decrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, -1) - 1; }
上面的方法无非就是通过CAS操作+1或者-1,+delta或者-delta,要么返回旧值,要么返回新值,所以都是一些复合操作,上面的方法都是调用了Unsafe中的getAndInt()方法,那么我们继续跟进到Unsafe中看看这个方法。
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; }
上面这个方法是不是非常的熟悉,通过CAS操作,成功则返回,失败则继续自旋。流程图如下:
上面的几个方法是对value的复合操作,不像第一类操作,能够直接通过关键字volatile就可以保证线程安全,而由于volatile不具备原子性,所以对value的符合操作需要通过volatile+CAS保证线程安全。
1:对value的单一操作:直接通过volatile保证线程安全。 2:对value的复合操作:通过volatile+CAS保证线程安全。
第三类操作:通过传递功能接口实现对value更复杂的操作
那什么是对value更复杂的操作呢?第二类复合操作只是在原来value的基础上加上一个值或者减去一个值,那么我们向在value的基础上乘上一个值或者除以一个值,或者更加复杂的运算,上面的方法就无法使用了,那么为了解决这个问题就出现了对value更加复杂的操作,例如:3*value+3这种操作,AtomicInteger就为我们提供了这些方法,通过传递一个功能接口,来实现我们想要的,而这个功能接口有一个方法需要我们实现,而复杂的运算就在这个方法中。首先我们看一下这个功能接口:
@FunctionalInterface public interface IntUnaryOperator { /** * Applies this operator to the given operand. * * @param operand the operand * @return the operator result */ int applyAsInt(int operand); } ------------------------------ @FunctionalInterface public interface IntBinaryOperator { /** * Applies this operator to the given operands. * * @param left the first operand * @param right the second operand * @return the operator result */ int applyAsInt(int left, int right); }
功能接口一般会有FunctionalInterface注解,说明是一个功能接口,我们通过applyAsInt()方法实现我们的逻辑。那继续看AtomicInteger里面的方法:
//向功能接口方法传递value,并通过CAS+自旋原子操作,然后返回旧值 public final int getAndUpdate(IntUnaryOperator updateFunction) { int prev, next; do { prev = get(); next = updateFunction.applyAsInt(prev); } while (!compareAndSet(prev, next)); return prev; } //向功能接口方法传递value,并通过CAS+自旋原子操作,然后返回新值 public final int updateAndGet(IntUnaryOperator updateFunction) { int prev, next; do { prev = get(); next = updateFunction.applyAsInt(prev); } while (!compareAndSet(prev, next)); return next; } //向功能接口方法传递value和指定的x,并通过CAS+自旋原子操作,然后返回旧值 public final int getAndAccumulate(int x, IntBinaryOperator accumulatorFunction) { int prev, next; do { prev = get(); next = accumulatorFunction.applyAsInt(prev, x); } while (!compareAndSet(prev, next)); return prev; } //向功能接口方法传递value和指定的x,并通过CAS+自旋原子操作,然后返回新值 public final int accumulateAndGet(int x, IntBinaryOperator accumulatorFunction) { int prev, next; do { prev = get(); next = accumulatorFunction.applyAsInt(prev, x); } while (!compareAndSet(prev, next)); return next; }
有的同学还是不理解这个功能接口,我对这一类举一个例子:
public class AtomicIntegerTest1 { private static AtomicInteger ai = new AtomicInteger(1); public static void main(String[] args) { //将原来值扩大到原来的2倍 ai.getAndUpdate(new IntUnaryOperator() { @Override public int applyAsInt(int operand) { return operand * 2; } }); System.out.println("ai扩大为原来的2倍:" + ai.get()); //将原来的值扩大原来3倍,在减去指定的x //left:就是value //right:就是我们指定的10 ai.accumulateAndGet(10, new IntBinaryOperator() { @Override public int applyAsInt(int left, int right) { return 3 * left - right; } }); System.out.println("ai扩大为原来的3倍,在减去指定的值:" + ai.get()); } }
运行结果如下:
通过上面的运行结果,大家是不是理解了这一类方法的作用。
第四类操作:对Number方法的实现
public int intValue() { return get(); } public long longValue() { return (long)get(); } public float floatValue() { return (float)get(); } public double doubleValue() { return (double)get(); }
这一类方法非常的简单了,都是调用get()获取value,然后转化成不同的基本类型。
上面就是AtomicInteger的全面内容了,通过上面的讲解,大家是否对AtomicInteger有了一个更加清晰的理解呢?如有什么不明白的地方,欢迎留言交流。
如果对你有所帮助,请续关注并转发。感谢支持