在上篇文章中我们介绍了JMM和其一些特性,java中还有一大神器就是关键volatile,可以说是和synchronized各领风骚,其中奥妙,我们来共同探讨下。
volatile
volatile是一种同步机制,比synchronized或者lock相关类更轻量,因为使用volatile并不会发生上下文切换等开销很大的行为。
voaltile不适应与a++。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import java.util.concurrent.atomic.AtomicInteger;
public class NoVolatile implements Runnable {
private static volatile int a = 0; private static AtomicInteger atomicInteger = new AtomicInteger();
@Override public void run() { for (int i = 0; i < 100; i++) { a++; atomicInteger.incrementAndGet(); } }
public static void main(String[] args) throws InterruptedException { NoVolatile noVolatile = new NoVolatile(); Thread threadA = new Thread(noVolatile); Thread threadB = new Thread(noVolatile); threadA.start(); threadB.start(); threadA.join(); threadB.join(); System.out.println(a); System.out.println(atomicInteger); } }
|

volatile适用场合:boolean flag,如果一个共享变量自始至终只被各个线程赋值,而没有其他操作,那么就可以用volatile来替代sycnchronized或者替代原子变量,因为赋值本身是有原子性的,而volatile又保证了可见性且禁止重排序,所以就足以保证线程安全。
volatile还具有happends-before规则: 线程1写入了volatile变量v, 接着线程2读取了变量v, 那么线程1写入的v以及之前的操作对线程2都是可见的。
原子性
一系列的操作,要么全部执行成功,要么全部不执行,是不可分割的,这就是原子性。i++不是原子性的,它分为三步:从变量i中读取i的值->值+1->将+1后的值写回i中。当多个线程同时i++时,就会如下图一样,i的值有可能被吞掉。

用synchronized可以保证原子性,因为synchronized修饰的代码块只能同时被一个线程执行。
java中的原子操作:
- 出了long和double以外的基本类型的赋值操作。
- 所以引用reference的赋值操作,不管是32位的机器还是64位的机器
- j.u.c下面的atomic包中所有类的原子操作
在以前32位的JVM上,long和double的操作不是原子的,但是在现代64位JVM上是原子的。还需要注意的是:原子操作 + 原子操作 != 原子操作。
单例模式
饿汉式,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public class Singleton1 {
private static final Singleton1 instance = new Singleton1();
private Singleton1() {}
public static Singleton1 getInstance() { return instance; } }
|
懒汉式,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
public class Singleton2 {
private static Singleton2 instance;
private Singleton2() {}
public static Singleton2 getInstance() { if (instance == null) { instance = new Singleton2(); } return instance; } }
|
当多个线程同时调用getInstance()方法时,可能会导致instance = new Singleton2();会执行多次,导致instance有多个实例。所以我们可以对getInstance()进行加锁,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
public class Singleton3 {
private static Singleton3 instance;
private Singleton3() {}
public static synchronized Singleton3 getInstance() { if (instance == null) { instance = new Singleton3(); } return instance; } }
|
这种做法虽然可以保证线程安全,但是效率太低。我们可以进行双重加锁校验来提高效率,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
public class Singleton4 {
private static Singleton4 instance;
private Singleton4() {}
public static Singleton4 getInstance() { if (instance == null) { synchronized (Singleton4.class) { if (instance == null) { instance = new Singleton4(); } } } return instance; } }
|
这种做法虽然效率提高了,但是还是存在瑕疵,因为instance = new Singleton4();不是原子操作的。事实上在JVM中这句代码做了下面三件事情:
- 给instance分配内存
- 调用Singleton4的构造函数来初始化成员变量
- 将instance对象指向分配的内存空间
在JVM的即时编译存在指令的重排序的优化。也就是说上面的第二步和第三步顺序是不能完全保证的,最终的执行顺序可能是1-2-3也可能是1-3-2。如果是1-3-2,则在3执行完毕、2未执行之前,线程一刚好被调度器暂停,此时线程二刚刚进来第一重检查,看到的instance已经不是null了,线程二访问到的是一个还未初始化完成的对象。所以我们可以使用volatile关键字来禁止指令的重排序。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
public class Singleton5 {
private static volatile Singleton5 instance;
private Singleton5() {}
public static Singleton5 getInstance() { if (instance == null) { synchronized (Singleton5.class) { if (instance == null) { instance = new Singleton5(); } } } return instance; } }
|
还可以用静态内部类实现单例模式,静态内部类的优点是:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。即当Singleton6第一次被加载时,并不需要去加载Single,只有当getInstance()方法第一次被调用时,才会去初始化INSTANCE,第一次调用getInstance()方法会导致虚拟机加载Singler类,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
public class Singleton6 {
private Singleton6() {}
private static class Single { private static final Singleton6 INSTANCE = new Singleton6(); }
public static Singleton6 getInstance() { return Single.INSTANCE; } }
|
在<>中说道,最佳的单例实现模式就是枚举模式。利用枚举的特性,让JVM来帮我们保证线程安全和单一实例的问题。除此之外,还不会因为反射机制和序列化而破环其单例。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
public enum Singleton7 { INSTANCE;
public void doSomething() {
} }
|