您的位置:首页 > Web前端

JUC源码分析1-原子变量-AtomicInteger/AtomicBoolean/AtomicLong/AtomicReference

2016-04-28 19:43 696 查看
记录学习中的一些东西,防止以后遗忘,参考了很多别人的文章,感谢之!

多线程并发操作时,对普通变量++或--不具有原子性,每次读取的值都不一样,看代码:

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;


参考:
http://brokendreams.iteye.com/blog/2250109 http://ifeve.com/juc-atomic-class-lazyset-que/ http://ifeve.com/jmm-cookbook-mb/ http://www.cnblogs.com/Mainz/p/3556430.html http://blog.csdn.net/aesop_wubo/article/details/7537278 http://www.68idc.cn/help/buildlang/ask/20150615369457.html http://www.docjar.com/html/api/sun/misc/Unsafe.java.html http://blog.csdn.net/fenglibing/article/details/17138079 http://www.infoq.com/cn/articles/java-memory-model-4/
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: