VOLATILE, SYNCHRONIZED AND LOCK

649 阅读3分钟

volatile

volatile /’vɒlətaɪl/ : ( often disapproving ) ( of a person or their moods 人或其情绪 ) changing easily from one mood to another 易变的;无定性的;无常性的。
volatile 用于标识一个Java变量应该被存储在主存,而不是CPU缓存。

volatile要解决的问题

  1. Variable Visibility Problems
    多个运行在多个CPU的线程使用同一个non-volatile变量,由于提升效率的原因,每个线程会copy这个变量值到不同的CPU的cache中,如下图:

    在Java中CPU的cache被抽象成为一个叫做工作内存的概念
  2. The Java volatile Visibility Guarantee
    Java提供Full volatile Visibility Guarantee。具体来讲,就是当线程写入一个volatile变量,那么对该线程写入volatile变量之前的所有的可见的所有变量,对会被写入主存,如果线程先读取了volatile变量,那么其他在读取volatile变量之后的所有线程可见变量也都会从主存读取
  3. Instruction reordering Chanlleges
    volatile 关键字另一个作用是让编译器意识到这个变量不能进行编译器reorder优化,,编译器reorder是为了性能问题而所做的操作。而指令重排会导致对Java的Full volatile Visiblility Guarantee的破坏,所有Java祭出下面的解决方案。
  4. The Java volatile Happens-Before Guarantee
    Happens-Before Guarantee保证了reordering情况下,也不破坏Java的 Full volatile Visibility Guaratee原则
  5. volatile is not always enough
    当对个线程对同一volatile变量进行读和写操作的时候,依然会出现Visibility Problems,这次问题出主存上在读写的间隔时间的多线程同步问题,也就是会出现race condition
    解决的办法是synchronized block或者Java concurrent包中的AtomicLong 或者AtomicReference这样的工具类来解决。
  6. volatile的缺点
    直接读写主存代价比直接读写工作内存也就是CPU的cache代价高昂的多,另外Java对volatile变量的Full volatile Guarantee 和 Java volatile Happens-Before Guarantee组织了指令重排的可能,而指令重排是一项正常的性能优化技术,所以应该在必要时候才使用volatile,而不是随意添加
    具体详见:http://tutorials.jenkov.com/java-concurrency/volatile.html

双重校验单例模式中的volatile的作用

public class Singleton {

    private Object lock;
    private volatile Singleton instance;

    private Singleton() {
    }

    /*
     1. 经典的双重检查,讲对单例对象的读写分离,提高性能
     2. 由于CPU指令重排的问题,会让多线程情况下第一次校验instance == null 结果为false,是线程得到一个不可用的对象实例
        所以使用Java关键字volatile,阻止编译器指令重排,这就是为什么instance对象要加volatile
      */
    public Singleton getInstance() {
        if (instance == null) {
            synchronized (lock) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

所以volatile 现在有两个作用:

  1. 防止指令重排
  2. JMM的内存一致性

synchronized

synchronzied 是一种隐式锁
synchronzied 方法 = synchronized(this)
synchronzied 静态方法 = yschronzied(class)
synchronized 使用monitor lock or intrinsic lock
monitor被绑定到一个对象
所有的隐式monitor都实现了重入机制reentrant
A thread can safely acquire the same lock multiple times without running into deadlocks (e.g. a synchronized method calls another synchronized method on the same object).
重入机制就是一个线程可以多次重复请求同一个锁,而不会发生死锁

并发包中的锁

Instead of using implicit locking via the synchronized keyword the Concurrency API supports various explicit locks specified by the Lock interface. Locks support various methods for finer grained(细粒度的) lock control thus are more expressive than implicit monitors.

实现了lock接口的锁提供了更多的锁控制机制,因此也比synchronized 更加昂贵

ReentrantLock 重入锁(一种排他锁)

有几个细粒度的方法:
lock()阻塞式
tryLock() 非阻塞式
isLocked()
isHeldByCurrentThread()
unlock()