首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >实例变量的懒初始化

实例变量的懒初始化

作者头像
jeremyxu
修改2023-09-20 17:09:55
2K0
修改2023-09-20 17:09:55
举报

今天遇到一个很有趣的问题,由于业务要求,需要懒初始化一个实例变量。

简单方法

很顺手就写出下面的代码。

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 件事情。

  1. 给obj分配内存
  2. 调用Object的构造函数来初始化成员变量
  3. 将obj对象指向分配的内存空间(执行完这步obj就为非null了)

这个就是JVM很有特色的指令重排序优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在3执行完毕、2 未执行之前,被另一个线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),这个线程拿着这个obj引用去干活,自然就会出问题。

规避指令重排序优化的办法

  1. 使用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陷阱,抽时间要重读一读这本书了。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016-06-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简单方法
  • 加个锁吧
  • 双重校验
  • 规避指令重排序优化的办法
  • Java单例
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档