面试准备--volatile和synchronized

485 阅读2分钟

volatile

volatile的作用

当一个变量被volatile关键字修饰之后,就确保了:

  1. 可见性。当一个线程对这个值修改之后,其他线程会得到其最新值
  2. 有序性。禁止指令重排

volatile关键字并不能保证变量的原子性。

volatile关键字的原理:

在加入volatile关键字之后,会多出一个lock前缀指令,相当于一个内存屏障,有三个功能:

  1. 确保指令重排的时候,不会把屏障前面的指令排到屏障后面,也不会把屏障后面的指令拍到屏障前面
  2. 强制对缓存的修改写入主存
  3. 如果是写操作,会使其他cpu的缓存无效

volatile关键字的使用场景:

  1. 标记状态量
volatile boolean flag = ture;
while (flag) {
    doSometing();
}
  1. 双重校验
public class Singleton() {
    private volatile static Singleton instance = null;
    private Singleton(){};
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized(Singleton.class){
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

synchronized

synchronized的用法:

分类 具体分类 被锁对象 伪代码
方法 实例方法 类的实例对象 public synchronized void method(){}
方法 静态方法 类对象 public synchronized static void method(){}
代码块 实例对象 类的实例对象 synchronized(this) {}
代码块 class对象 类对象 synchronized(SynchronizedTest.class){}
代码块 任意实例对象object 实例对象object String lock = "lock";
synhronized(lock){}

如果锁的是类对象的话,尽管new了多个对象,但他们之间任然属于同一个类,会被锁住。即线程之间保证同步关系。

sychronized原理

  1. 同步代码块 在执行synchronized代码块的时候,使用monitorenter指令,在执行完之后,使用monitorexit指令。每个对象有一个计数器,当获取到monitor时,计数器加一,当释放monitor时,计数器减一。在执行monitorenter指令时,当前线程先试图获取对象锁的monitor,当计数器为0的时候,获取成功,并将计数器置为1。如果当前线程拥有objectref的monitor持有权,再次进入的时候,无需执行monitorenter指令,并讲计数器加1,在执行monitorexit时,将计数器置为0。其它线程有机会获取monitor的持有权。
public void test() {
        synchronized(this) {

        }
    }

字节码

    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: aload_1
         5: monitorexit
         6: goto          14
         9: astore_2
        10: aload_1
        11: monitorexit
        12: aload_2
        13: athrow
        14: return
...

  1. 同步方法 同步方法是隐式的,通过ACC_SYNCHRONIZED标识来区分一个方法是否是同步方法。在调用方法时,如果检测到有ACC_SYNCHRONIZED标识,执行线程将先持有monitor,在方法执行结束后(无论是正常执行结束还是抛出异常),释放monitor。在该线程持有monitor时,其他线程无法获得同一个monitor。
public synchronized void test() {}
 public synchronized void test();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 14: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   LSynchronizedTest;
...