java设计模式之单例模式你真的会了吗?(懒汉式篇)

java设计模式之单例模式你真的会了吗?(懒汉式篇)

一、什么是单例模式?

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

二、单例模式之懒汉式有什么特点以及优缺点?

  • 构造方法私有化
  • 在第一次被使用时构建实例,延迟初始化
  • 对外提供统一的静态工厂方法返回实例
  • 优点:需要的时候才实例化所以节约内存。
  • 缺点:第一次加载时不够快,多线程使用时不必要的同步开销大。

三、懒汉式单例的代码进阶(1)

public class LazySingleton implements Serializable {
    private static final long serialVersionUID = -777413485350310911L;

    private LazySingleton() {}

    private static LazySingleton lazySingleton = null;

    public static LazySingleton getInstance() {
        if(lazySingleton == null){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}
  • 上面的代码存在着最明显的问题就是在多线程的环境下无法保证单例。

四、懒汉式单例的代码进阶(2)

public class LazySingleton implements Serializable {
    private static final long serialVersionUID = -777413485350310911L;

    private LazySingleton() {}

    private volatile static LazySingleton lazySingleton = null;

    synchronized public static LazySingleton getInstance() {
        if(lazySingleton == null){
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}
  • 上面的代码在getInstance()方法前面增加了关键字synchronized进行线程锁,以处理多个线程同时访问的问题。但是,这样虽然解决了线程安全问题,但是每次调用getInstance()时都需要进行线程锁定判断,在多线程高并发访问环境中,将会导致系统性能大大降低。

五、懒汉式单例的代码进阶(3)

public class LazySingleton implements Serializable {
    private static final long serialVersionUID = -777413485350310911L;

    private LazySingleton() {}

    private volatile static LazySingleton lazySingleton = null;

    //double check
    public static LazySingleton getInstance() {
        if(lazySingleton == null){
            synchronized(LazySingleton.class){
                if(lazySingleton == null){
                    lazySingleton = new LazySingleton();
                }
            }
        }
        return lazySingleton;
    }
}
  • 上面的代码采用synchronized和double check的方式成功解决了线程安全问题以及提高了多线程下的性能,但是性能任然不够理想。
  • lazySingleton采用 volatile 关键字修饰也是很有必要的, lazySingleton = new LazySingleton(); 这段代码其实是分为三步执行:
    (1)为 lazySingleton 分配内存空间
    (2)初始化 lazySingleton
    (3)将 lazySingleton 指向分配的内存地址
    但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getInstance() 后发现
    lazySingleton 不为空,因此返回 lazySingleton,但此时 lazySingleton 还未被初始化。使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。

六、懒汉式单例的代码进阶(4)

public class LazySingleton implements Serializable {
    private static final long serialVersionUID = -777413485350310911L;

    private LazySingleton() {}

    public static LazySingleton getInstance() {
        return LazyInnerSingleton.INSTANCE;
    }

    /**
     * 利用内部类的特性创建单例
     */
    private static class LazyInnerSingleton {
        private static final LazySingleton INSTANCE = new LazySingleton();
    }
}
  • 上面利用内部类的特性创建单例既保证了线程安全(由jvm的类加载机制提供)问题又提高了性能,但是还是存在着一个问题:利用反射依然可以破坏“单例”,所以继续改进代码。

六、懒汉式单例的代码进阶(5)

public class LazySingleton implements Serializable {
    private static final long serialVersionUID = -777413485350310911L;

    private LazySingleton() {
        //防止利用反射破坏单例
        if(LazyInnerSingleton.INSTANCE != null){
            throw new RuntimeException("不允许构建多个实例!");
        }
    }

    public static LazySingleton getInstance() {
        return LazyInnerSingleton.INSTANCE;
    }

    /**
     * 利用内部类的特性创建单例
     */
    private static class LazyInnerSingleton {
        private static final LazySingleton INSTANCE = new LazySingleton();
    }
}
  • 在构造方法中抛出一个异常以防止通过反射破坏单例。

PS:如果你看到了这篇文章,并且觉得对你有帮助,请给个关注和点赞,谢谢!

posted @ 2021-05-18 15:04  野生D程序猿  阅读(500)  评论(0编辑  收藏  举报