转载自:http://www.cnblogs.com/Mainz/p/3556430.htmlhtml
synchronized关键字是基于阻塞的锁机制,也就是说当一个线程拥有锁的时候,访问同一资源的其它线程须要等待,直到该线程释放锁,这里会有些问 题:首先,若是被阻塞的线程优先级很高很重要怎么办?其次,若是得到锁的线程一直不释放锁怎么办?(这种状况是很是糟糕的)。还有一种状况,若是有大量的 线程来竞争资源,那CPU将会花费大量的时间和资源来处理这些竞争(事实上CPU的主要工做并不是这些),同时,还有可能出现一些例如死锁之类的状况,最 后,其实锁机制是一种比较粗糙,粒度比较大的机制,相对于像计数器这样的需求有点儿过于笨重,所以,对于这种需求咱们期待一种更合适、更高效的线程安全机制。java
Java中long赋值不是原子操做,由于先写32位,再写后32位,分两步操做,而AtomicLong赋值是原子操做,为何?为何 volatile能替代简单的锁,却不能保证原子性?这里面涉及volatile,是java中的一个我以为这个词在Java规范中从未被解释清楚的神奇 关键词,在Sun的JDK官方文档是这样形容volatile的:程序员
The Java programming language provides a second mechanism, volatile fields, that is more convenient than locking for some purposes. A field may be declared volatile, in which case the Java Memory Model ensures that all threads see a consistent value for the variable.算法
意思就是说,若是一个变量加了volatile关键字,就会告诉编译器和JVM的内存模型:这个变量是对全部线程共享的、可见的,每次jvm都会读取最新写入的值并使其最新值在全部CPU可见。volatile彷佛是有时候能够代替简单的锁,彷佛加了volatile关键字就省掉了锁。但又说volatile不能保证原子性(java程序员很熟悉这句话:volatile仅仅用来保证该变量对全部线程的可见性,但不保证原子性)。这不是互相矛盾吗?缓存
不要将volatile用在getAndOperate场合(这种场合不原子,须要再加锁),仅仅set或者get的场景是适合volatile的。安全
例如你让一个volatile的integer自增(i++),其实要分红3步:1)读取volatile变量值到local; 2)增长变量的值;3)把local的值写回,让其它的线程可见。这3步的jvm指令为:jvm
mov 0xc(%r10),%r8d ; Load inc %r8d ; Increment mov %r8d,0xc(%r10) ; Store lock addl $0x0,(%rsp) ; StoreLoad Barrier
注意最后一步是内存屏障。ide
内存屏障(memory barrier) 是一个CPU指令。基本上,它是这样一条指令: a) 确保一些特定操做执行的顺序; b) 影响一些数据的可见性(多是某些指令执行后的结果)。编译器和CPU能够在保证输出结果同样的状况下对指令重排序,使性能获得优化。插入一个内存屏障, 至关于告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。内存屏障另外一个做用是强制更新一次不一样CPU的缓存。例如,一个写屏障会 把这个屏障前写入的数据刷新到缓存,这样任何试图读取该数据的线程将获得最新值,而不用考虑究竟是被哪一个cpu核心或者哪颗CPU执行的。oop
内存屏障(memory barrier) 和volatile什么关系?上面的虚拟机指令里面有提到,若是你的字段是volatile,Java内存模型将在写操做后插入一个写屏障指令,在读操做 前插入一个读屏障指令。这意味着若是你对一个volatile字段进行写操做,你必须知道:一、一旦你完成写入,任何访问这个字段的线程将会获得最新的 值。二、在你写入前,会保证全部以前发生的事已经发生,而且任何更新过的数据值也是可见的,由于内存屏障会把以前的写入值都刷新到缓存。post
明白了内存屏障(memory barrier)这个CPU指令,回到前面的JVM指令:从Load到store到内存屏障,一共4步,其中最后一步jvm让这个最新的变量的值在全部线程可见,也就是最后一步让全部的CPU内核都得到了最新的值,但中间的几步(从Load到Store)是不安全的,中间若是其余的CPU修改了值将会丢失。下面的测试代码能够实际测试voaltile的自增没有原子性:
private static volatile long _longVal = 0; private static class LoopVolatile implements Runnable { public void run() { long val = 0; while (val < 10000000L) { _longVal++; val++; } } } private static class LoopVolatile2 implements Runnable { public void run() { long val = 0; while (val < 10000000L) { _longVal++; val++; } } } private void testVolatile(){ Thread t1 = new Thread(new LoopVolatile()); t1.start(); Thread t2 = new Thread(new LoopVolatile2()); t2.start(); while (t1.isAlive() || t2.isAlive()) { } System.out.println("final val is: " + _longVal); } Output:------------- final val is: 11223828 final val is: 17567127 final val is: 12912109
这是一段线程不安全的singleton(单例模式)实现,尽管使用了volatile:
public class wrongsingleton { private static volatile wrongsingleton _instance = null; private wrongsingleton() {} public static wrongsingleton getInstance() { if (_instance == null) { _instance = new wrongsingleton(); } return _instance; } }
就拿AtomicLong来讲,它既解决了上述的volatile的原子性没有保证的问题,又具备可见性。它是如何作到的?固然就是上文《非阻塞同步算法与CAS(Compare and Swap)无锁算法》提到的CAS(比较并交换)指令。 其实AtomicLong的源码里也用到了volatile,但只是用来读取或写入,见源码:
public class AtomicLong extends Number implements java.io.Serializable { private volatile long value; /** * Creates a new AtomicLong with the given initial value. * * @param initialValue the initial value */ public AtomicLong(long initialValue) { value = initialValue; } /** * Creates a new AtomicLong with initial value {@code 0}. */ public AtomicLong() { }
其CAS源码核心代码为:
int compare_and_swap (int* reg, int oldval, int newval) { ATOMIC(); int old_reg_val = *reg; if (old_reg_val == oldval) *reg = newval; END_ATOMIC(); return old_reg_val; }
虚拟机指令为:
mov 0xc(%r11),%eax ; Load mov %eax,%r8d inc %r8d ; Increment lock cmpxchg %r8d,0xc(%r11) ; Compare and exchange
由于CAS是基于乐观锁的,也就是说当写入的时候,若是寄存器旧值已经不等于现值,说明有其余CPU在修改,那就继续尝试。因此这就保证了操做的原子性。