原子变量-AtomicInteger/AtomicBoolean/AtomicLong/AtomicReference
记录学习中的一些东西,防止以后遗忘,参考了很多别人的文章,感谢之!
多线程并发操作时,对普通变量++或--不具有原子性,每次读取的值都不一样,看代码:
import java.util.concurrent.atomic.AtomicInteger;
public class Incr {
public AtomicInteger a = new AtomicInteger(0);
public int incrAtomic(){
return a.getAndIncrement();
}
public int getAtomic(){
return a.get();
}
public int b = 0;
public int incrInt(){
return b++;
}
public int getInt(){
return b;
}
}
import java.util.concurrent.CountDownLatch;
public class MultiThread {
private static Incr incr = new Incr();
public static void main(String[] args) {
final CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int j = 0; j < 100; j++) {
incr.incrAtomic();
incr.incrInt();
}
}
}).start();
}
countDownLatch.countDown();
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("AtomicInteger.incr: "+incr.getAtomic());
System.out.println("int.incr: " + incr.getInt());
}
}
100个线程,每个线程循环100次获取自增变量的值,运行结果显示,使用Atomic类型作为自增变量,最后的结果是1W,而使用普通变量,每次的结果都是不一样的。
查看AtomicInteger源码:
//原子变量的cas都是通过unsafe来操作
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); }
}
//volatile类型变量,保证value的可见性,原子性通过unsafe的cas操作来保证
private volatile int value;
ps:
1、 volatile只能保证变量的可见性,保证不了变量操作的原子性对volatile类型变量进行write操作,write前加storestore,write后加storeLoad内存屏障,把本地变量刷回主存,并且让其他处理器的cacheline失效,这样其他处理在read的时候发现cacheline失效就会去主存获取值,重新做缓存行填充单纯的对volatile类型变量的get/set操作没有问题,如果方法中get操作后有其他动作再进行set,会出问题,此时整体流程大概是4步:load->operation->store->storeLoad,多线程情况保证不了前3步不出问题,所以如果需要不如直接用普通变量,方法加锁来处理另外volatile也能防止指令重排;
有关内存屏障的详细说明请参考:http://ifeve.com/jmm-cookbook-mb/,来自于并发网翻译的Doug Lea的文章,very good。
2、 Unsafe提供了一些native方法可以用来操作系统底层进行操作,cas的操作和AQS的park/unpark都使用到unsafe的一些方法,所以过一遍Unsafe的源码,源码地址:http://www.docjar.com/html/api/sun/misc/Unsafe.java.html:;
juc里面使用到unsafe的时候一般通过Unsafe unsafe = Unsafe.getUnsafe();获取,如果我们自己代码这样使用,会抛出SecurityException,看getUnsafe的源码:
public static Unsafe getUnsafe() {
Class cc = sun.reflect.Reflection.getCallerClass(2);
if (cc.getClassLoader() != null)
throw new SecurityException("Unsafe");
return theUnsafe;
}
我们可以通过反射直接获取theUnsafe:
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
objectFieldOffset:非static变量的偏移
staticFieldOffset:static变量的偏移
getXXX:通过偏移量获取该实例的变量值
putXXX:通过偏移量直接设置该实例的变量值
putOrderedXXX:这个操作也是设置值,但是不会加storeLoad屏障
park、unpark:AQS里面的LockSupport会使用来挂起、解挂线程,这个和wait\notify不同,通过底层的一个变量(0、1)来处理
arrayBaseOffset:获取数组第一个元素的偏移
arrayIndexScale:获取数组的每次偏移的增量
ps了一堆,看下AtomicInteger中的重要方法:
/**
直接设置volatile变量的值
*/
public final void set(int newValue) {
value = newValue;
}
/**
putOrderedInt,去掉了storeLoad内存屏障,只保证最终设置成功,不保证多处理环境下,其他处理器read到最新的值
*/
public final void lazySet(int newValue) {
unsafe.putOrderedInt(this, valueOffset, newValue);
}
/**
loop循环,不断重试,直到成功
*/
public final int getAndSet(int newValue) {
for (;;) {
int current = get();
if (compareAndSet(current, newValue))
return current;
}
}
/**
Atomic中n多方法通过loop来调用这个方法,类似乐观锁,expect表示期望的值,update是更新的值
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
/**
代码跟compareAndSet没什么区别,
注释里面May fail spuriously and does not provide ordering guarantees,会导致伪失败,不保证指令有序
*/
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
AtomicLong比AtomicInteger多了个
static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();
记录底层是否支持long类型的cas操作,如果不支持会通过加锁实现cas,其他方法与AtomicInteger差不多。
AtomicBoolean也是用
private volatile int value;
public AtomicBoolean(boolean initialValue) {
value = initialValue ? 1 : 0;
}
每次操作时将传入的boolean类型转换为0,1,其他类同。
我们发现有基本类型int、long、boolean的原子操作,没有string类型,我们可以通过AtomicReference来实现string类型的原子操作,AtomicReference使用:
AtomicReference<String> atomicString = new AtomicReference<String>("helloWorld");
AtomicReference内部持有一个对象的引用:
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicReference.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile V value;