在并发的世界里,有两种心态--乐观和悲观。乐观的人总认为这事不一定发生,侥幸地先试一把再说。悲观的人认为,哦,这件事可能会有这样那样的负面,不能轻易尝试。这些对应到我们java的并发策略就加锁和无锁。
啥?并发还有无锁这种操作?真有,而且效率相当地高。为什么效率高呢?因为加锁,意味着就有获取锁,释放锁,等待锁,阻塞。。这些,会带来线程的切换,只要线程一切换,就会要保存现场,这样的操作都是代价昂贵的,我们不希望这样的情况发生(或者尽量避免)。 本文就分析在无锁情况下,如何保证并发的安全。答案就是cas. 先看一个demo。public class AtomicIntegerDemo { static AtomicInteger i = new AtomicInteger(); public static class AddThread implements Runnable{ @Override public void run() { for (int j = 0; j < 10000; j++) { i.incrementAndGet(); } } } public static void main(String[]s) throws InterruptedException{ Thread[] t= new Thread[10]; for (int j = 0; j < 10; j++) { t[j]= new Thread(new AddThread()); } for (int j = 0; j < 10; j++) { t[j].start(); } for (int j = 0; j < 10; j++) { t[j].join(); } System.out.println(i);//结果应该是10000*10 }}复制代码
这是为啥呢?一般有10个线程去执行一个i++肯定总数会小于10000*10的。 带着这个好奇我们先说下cas的铺垫,再看源码。
cas的基本思想如下: cas(当前值, 期望值,目标值)。当执行这个cas()这个方法时,方法内部会判断当前值是否等于期望值,若等,就把当前值设为目标值,返回true。不等,返回false(说明有别的线程更改了当前值)。那咋办呢?此时一般会有个for(;;)死循环,重试,直到返回ture。为什么可以重试,上面说了这里线程没锁,当前线程有了执行权,只要没被cpu调度,就一直拥有执行权,就一直再执行for循环。现在cpu已经保证了cas这个比较并交换指令是原子性的了。。也就是说这个cas()方法嗖的一下就执行完了,不会被打断。 现在看下incrementAndGet()
的实现。 public final int incrementAndGet(){ for(;;){ int current = get();//返回当前值 int next = current+1; if(compareAndSet(current,next)){ return next; } }}复制代码
啊,手欠,还是想看看compareAndSet(current,next)的实现,不然总感觉不踏实,对就是这种感觉,不知道读这篇文章的你,有没这种感觉。
public final boolean compareAndSet(int expect ,int update){return unsafe.compareAndSwapInt(this,valueOffset,expext,update);}复制代码
这里出现一个我们不熟悉的变量unsafe
,它是sun.misc.Unsafe
类型。我猜测这个类应该是封装了些不安全的操作。啥是不安全的呢?指针!因为指针如果指错了位置,就可能覆盖了别人的内存,导致系统崩溃。Java中屏蔽了这一玩意,当有时还必须请出这玩意,这里就是一个例子。
unsafe.compareAndSwapInt()
public final native boolean compareAndSwapInt(Object o, long offset, int expect, int x);
这个方法是native的,我们应用层面是调不到的。这个方法内部就是使用了上面的cas()思想,原子指令完成。 上面的unsafe是通过这个方法获得的 public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(); if((var0.getClassLoader() !=null) { throw new SecurityException("Unsafe"); } else { return theUnsafe; } }复制代码
上面的代码会检查调用getUnsafe()
的类的classLoader
是否为null。这就使得我们的应用程序无法使用Unsafe类。