序
阅读java源码可能是每一个java程序员的必修课,只有知其所以然,才能更好的使用java,写出更优美的程序,阅读java源码也为我们后面阅读java框架的源码打下了基础。阅读源代码其实就像再看一篇长篇推理小说一样,不能急于求成,需要慢慢品味才行。这一系列的文章,记录了我阅读源码的收获与思路,读者也可以借鉴一下,也仅仅是借鉴,问渠那得清如许,绝知此事要躬行!要想真正的成为大神,还是需要自己亲身去阅读源码而不是看几篇分析源码的博客就可以的。
正文
最近在看JAVA1.8线程池源码的时候,发现对于AtomicInteger类的使用不太熟悉,所以就专门研究了一下,有一些收获。AtomicInteger是juc(java.util.concurrent)包下提供的一个可以原子性操作Integer对象的类。通过它,我们可以很方便的对Integer进行线程安全的加、减、改值等操作。其原理是使用的CAS无锁算法。
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
//JAVA实现CAS算法的类,整个类有关线程安全的操作,都是借助它来实现。
private static final Unsafe unsafe = Unsafe.getUnsafe();
//变量value的内存首地址的偏移量。
private static final long valueOffset;
//静态代码块,在类加载时运行
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//存放int值
private volatile int value;
1.unsafe 其实就是负责与CAS相关的操作实现,这里需要提一下的是Unsafe是不符合JAVA标准的,官方不推荐大家在编写自己的中使用它,因为它引入了和C++中指针相似的东西,但可以借用java.util.concurrent.atomic包下的类来使用。
2.valueOffset的值是变量value的内存首地址的偏移量。,它在AtomicInteger被加载时就被赋值了。
3.value其实就是存放实际值的变量,它被volatile 关键字修饰,说明对于其他线程是可见的。
发现并没有任何描述,但是通过百度发现说是value在内存中首地址。出于严禁的角度,我就写了一个测试来看看:
public class Main {
public static void main(String[] args) {
AtomicInteger a=new AtomicInteger(1);
AtomicInteger b=new AtomicInteger(1);
int c=a.addAndGet(5);
b.addAndGet(4);
System.out.println(a.get());
System.out.println(c);
}
}
addAndGet()方法的定义与声明
这个测试中,我实例化了两个AtomicInteger 对象:a和b,同时在调用的方法addAndGet()中打了断点。
最后debug发现,两个变量的valueOffset值都为12。说明valueOffset就是value内存首地址的偏移量。到这里或许大家还想问,这个偏移应该是针对于什么的偏移呢?你偏移量总得有个参照位置吧。这里就要回到我们valueOffset的定义与赋值了。
private static final long valueOffset;
//静态代码块,在类加载时运行
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
我们可以看到,valueOffset 是一个静态常量,并且在类加载时就被赋值了。那么这个类在编译后的字节码是一定的,但是存放对象的首地址是随机的,所以这里的偏移应该是相对于对象实例的首地址。如果大家看不明白,请看下面这幅图,这幅图简介明了的描述了valueOffset 的含义。
再联系到AtomicInteger在调用unsafe接口时,都将this和valueOffset 作为传入参数(如下图),我们可以了解到unsafe实现的CAS算法是依赖底层实现的(因为方法声明有native关键字),我们为其提供具体的内存地址,即this+valueOffset ,就可以让其找到要进行原子性操作的变量value,并进行修改。