记录学习中的一些东西,防止以后遗忘,参考了很多别人的文章,感谢之!
多线程并发操作时,对普通变量++或--不具有原子性,每次读取的值都不一样,看代码:
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;
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);
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); }
static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();
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/