什么是原子操作
原子操作是指不会被线程调度机制打断的操作,在JAVA中i++ 是原子操作吗?答案是否定否定的。用一段代码做一个测试,count 计数器由于不是原子操作所以会产生无法预料的结果。
static AtomicInteger atomicCount = new AtomicInteger(0);
static Integer count = new Integer(0);
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[500];
for (int i = 0; i < 500; i++) {
threads[i] =
new Thread(() -> {
for (int j = 0; j < 10; j++) {
atomicCount.addAndGet(1);
count++;
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
for (int i = 0; i < 500; i++) {
threads[i].start();
}
for (int i = 0; i < 500; i++) {
threads[i].join();
}
System.out.println("atomicInteger = " + atomicCount.get());
System.out.println("count = " + count);
}
走近 atomic*
atomic* 由 Doug Lea (JUC几乎由他一人完成) 大神设计。在上面的测试中,见识了AtomicInteger的魔力,那么不知道你有没有同样的疑问,在多线程中,如果只能保证原子性是否能线程安全呢?在不使用synchronized(使用JVM调用指令保证原子性,优化后的synchronized没有研究过,如果你知道不妨留言告知。) 关键字的情况下又如何保证原子性!如果你看过源码就会发现, atomic* 通过 可见性 + 原子性保证线程安全。
- 什么是可见性
见性
可见性是指一个线程修改变量后,对其它线程是立即可见的。
java中volatile保证可见性, 但是这并不意味着使用了volatile的变量就是线程安全的。在AtomicInteger中可以看到,他的值是通过 volatile 修饰的。
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// volatile 修饰的变量,保证可见性
private volatile int value;
/**
* Creates a new AtomicInteger with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicInteger(int initialValue) {
value = initialValue;
}
- 原子操作
原子操作开篇已经描述过了,直接进入正题,在atomic*中使用 unsafe是原子操作的核心。unsafe作为BUG级的存在带给我直接操作内存的便利,这点和 C , C++ 的指针相同, 所以并不提倡我们直接使用。在unsafe中compareAndSwapInt(
CAS更多详细介绍)
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
compareAndSwapInt 的逻辑是这样的
function cas(p : pointer to int, old : int, new : int) returns bool {
if *p ≠ old {
return false
}
*p ← new
return true
}
p指向的位置,与预期的值比较, 不相等就返回false, 否则返回true 并把新的赋值给老的值。
- 非阻塞算法 (nonblocking algorithms)
有了可见性、 原子性, 还是无法保证我们在多线程能够得到预期结果因为在上面代码中compareAndSwapInt尝试一次,就会返回结束。在这里引入(非阻塞算法 (nonblocking algorithms))可以通过代码发现。
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;
}
可以看到当不满足交换条件时,线程会一直阻塞下去, 但是代码级的阻塞不影响其它线程的执行(JVM线程等待后需要被唤醒)从而也会产生系统开销。
AtomicInteger API 以及应用
- API
int getAndAdd(int delta) 原子更新并且返回更新前的值
int addAndGet(int delta) 原子添加并返回当前值.
int decrementAndGet() 原子减一
int get() 得到当前值
boolean compareAndSet(int expect, int update) 如果当前值等于期望值,则按原子值将该值设置为给定的更新值。(不会等待)
void lazySet(int newValue) 最终设置为给定值,通过文档翻译过来的,这个方法有些科幻更多参见这里
没有罗列所有API 但是其它的API也大同小异
- 应用
Atomic主要应用JUC后续的API中,比如读写锁, 共享锁,信号量。在之后将会更多看到Atomic的身影。自身在应用中没有实际使用过Atomic的API,但是借鉴的意义很大,比如使用Redis做分布式锁。