实例变量的懒初始化
文章目录
今天遇到一个很有趣的问题,由于业务要求,需要懒初始化一个实例变量。
简单方法
很顺手就写出下面的代码。
public class LazyFieldInitializer {
private Object obj = null;
public LazyFieldInitializer(){
}
public void someOp(){
if(obj == null){
obj = new Object();
}
}
public void otherOp(){
}
public static void main(String[] args) {
LazyFieldInitializer instance = new LazyFieldInitializer();
instance.someOp();
}
}
但这种方法存在问题,线程不安全,当两个线程同时调用someOp方法,obj变量被初始化了两次。
加个锁吧
public class LazyFieldInitializer {
private Object obj = null;
public LazyFieldInitializer(){
}
public void someOp(){
synchronized (this) {
if (obj == null) {
obj = new Object();
}
}
}
public void otherOp(){
}
public static void main(String[] args) {
LazyFieldInitializer instance = new LazyFieldInitializer();
instance.someOp();
}
}
这种方法虽说没问题,就是效率不高,每次执行someOp方法都会锁this。
双重校验
好了,来个双重校验吧
public class LazyFieldInitializer {
private Object obj = null;
public LazyFieldInitializer(){
}
public void someOp(){
if (obj == null) {
synchronized (this) {
if(obj == null) {
obj = new Object();
}
}
}
}
public void otherOp(){
}
public static void main(String[] args) {
LazyFieldInitializer instance = new LazyFieldInitializer();
instance.someOp();
}
}
这次没问题了吧。很可惜还是有问题,关键在obj = new Object();
这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。
- 给obj分配内存
- 调用Object的构造函数来初始化成员变量
- 将obj对象指向分配的内存空间(执行完这步obj就为非null了)
这个就是JVM很有特色的指令重排序优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在3执行完毕、2 未执行之前,被另一个线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),这个线程拿着这个obj引用去干活,自然就会出问题。
规避指令重排序优化的办法
- 使用volatile关键字禁止指令重排序优化
public class LazyFieldInitializer {
private volatile Object obj = null;
public LazyFieldInitializer(){
}
public void someOp(){
if (obj == null) {
synchronized (this) {
if (obj == null) {
obj = new Object();
}
}
}
}
public void otherOp(){
}
public static void main(String[] args) {
LazyFieldInitializer instance = new LazyFieldInitializer();
instance.someOp();
}
}
volatile
关键字在这里有两层含义,一个是禁止JVM对该变量的指令重排序优化,另一个是使这个变量的修改对其它线程可见。
Java单例
查阅JVM的指令重排序优化相关文章,还看到Java单例写法的文章,这里小小总结一下。
/**
* Created by jeremy on 16/6/11.
* 懒汉模式, 线程不安全
*/
public class Singleton1 {
private static Singleton1 instance;
private Singleton1(){
}
public void sayHello(){
System.out.println("hello");
}
public static Singleton1 getInstance(){
if(instance == null){
instance = new Singleton1();
}
return instance;
}
public static void main(String[] args) {
Singleton1.getInstance().sayHello();
}
}
/**
* Created by jeremy on 16/6/11.
* 懒汉模式, 线程安全
*/
public class Singleton2 {
private static Singleton2 instance;
private Singleton2(){
}
public void sayHello(){
System.out.println("hello");
}
public static synchronized Singleton2 getInstance(){
if(instance == null){
instance = new Singleton2();
}
return instance;
}
public static void main(String[] args) {
Singleton2.getInstance().sayHello();
}
}
/**
* Created by jeremy on 16/6/11.
* 饿汉模式, 类变量类加载时初始化, 线程安全
*/
public class Singleton3 {
private static Singleton3 instance = new Singleton3();
private Singleton3(){
}
public void sayHello(){
System.out.println("hello");
}
public static Singleton3 getInstance(){
return instance;
}
public static void main(String[] args) {
Singleton3.getInstance().sayHello();
}
}
/**
* Created by jeremy on 16/6/11.
* 饿汉模式, 类变量类加载时在类的静态初始化块里初始化, 线程安全
*/
public class Singleton4 {
private static Singleton4 instance = null;
static {
instance = new Singleton4();
}
private Singleton4(){
}
public void sayHello(){
System.out.println("hello");
}
public static Singleton4 getInstance(){
return instance;
}
public static void main(String[] args) {
Singleton4.getInstance().sayHello();
}
}
/**
* Created by jeremy on 16/6/11.
* 懒汉模式, 采用内部静态类,线程安全
*/
public class Singleton5 {
private static class Singleton5Holder {
private static final Singleton5 instance = new Singleton5();
}
private Singleton5(){
}
public void sayHello(){
System.out.println("hello");
}
public static Singleton5 getInstance(){
return Singleton5Holder.instance;
}
public static void main(String[] args) {
Singleton5.getInstance().sayHello();
}
}
/**
* Created by jeremy on 16/6/11.
* 懒汉模式, 枚举实现, 线程安全
*/
public enum Singleton6 {
INSTANCE;
Singleton6(){
}
public void sayHello(){
System.out.println("hello");
}
public static void main(String[] args) {
Singleton6.INSTANCE.sayHello();
}
}
/**
* Created by jeremy on 16/6/11.
* 懒汉模式, 双重校验, 采用volatile规避指令重排序优化, 线程安全
*/
public class Singleton7 {
private static volatile Singleton7 instance;
private Singleton7(){
}
public void sayHello(){
System.out.println("hello");
}
public static Singleton7 getInstance(){
if(instance == null){
synchronized (Singleton7.class){
if(instance == null){
instance = new Singleton7();
}
}
}
return instance;
}
public static void main(String[] args) {
Singleton7.getInstance().sayHello();
}
}
总结
Java还是有不少暗坑的,写代码时得小心了。记得大学时看过Java Puzzlers,那上面列举过不少这样的Java陷阱,抽时间要重读一读这本书了。
文章作者 Jeremy Xu
上次更新 2016-06-11
许可协议 © Copyright 2020 Jeremy Xu