单例模式应该是每个程序员最熟悉的设计模式之一了,在我们最初学习阶段的时候,应该就或多或少的听说过单例模式,指的就是系统中运行时某个类只能存在唯一的示例,为了解决资源浪费而产生的设计模式,如Hibernate中的SessionFactory,就使用了单例模式,本文将用极简的语言和丰富的示例去讲透单例模式。
Java中的单例模式,一共有五种大的实现方式,本文将饿汉式以及懒汉式分割为五种情况,分别介绍每种情况的优劣势。并且会在文章最后做出一个总结,如果不想看文章详细内容可以直接拉到最后看总结。
单例模式的核心思想就是不能让外部去调用该类的new方法,已知Java是通过构造器去创建对象的,所以第一部都是私有化构造器,避免外部使用new方式去创建对象,并且要有一个方法去返回当前对象这个唯一的示例。 懒汉式即当前类加载,就创建对象实例,饿汉式即需要的时候才会创建当前类实例,所以懒汉式会造成资源浪费,而饿汉式需要考虑如何解决多线程下的线程安全问题。
- 饿汉式
- 静态常量饿汉式
缺点:不管这个对象是否需要 只要当Singleton类被实例化的时候,对象就会直接被创建,容易导致资源浪费,不推荐使用class Singleton { // 私有化构造器 private Singleton() { } // 创建当前类对象 private final static Singleton instance = new Singleton(); // 返回当前类实例 public static Singleton getInstance() { return instance; } }
- 静态代码块饿汉式
class Singleton { // 私有化构造器 private Singleton() { } // 静态代码块中创建对象实例 static { instance = new Singleton(); } // 创建当前类对象 private static Singleton instance; // 返回当前类实例 public static Singleton getInstance() { return instance; } }
- 静态常量饿汉式
- 懒汉式
-
普通懒汉式
class Singleton { // 私有化构造器 private Singleton() { } // 创建当前类对象 private static Singleton instance; // 返回当前类实例 public static Singleton getInstance() { // 如果当前实例为空 则创建 否则返回 if (instance == null) { System.out.println("instance为空"); instance = new Singleton(); } else { // 这里存在的意义是为了做一下多线程的测试 就知道创建了多少对象了 System.out.println("instance不为空"); } return instance; } }
缺点:普通的懒汉式会出现线程安全问题,如下图所示,只适用于单线程环境中可以使用这种方式
-
同步方法懒汉式
这里省略代码了,其实就是在获取实例的方法上 添加上一个同步关键字
优点:解决了线程安全问题。
缺点:由于一旦有一个进入了方法,则其余会进行等待,有可能会导致多线程环境下的执行缓慢。 -
同步代码块懒汉式
实现的方式就是将synchronized关键字从方法上 转移到if上面 实现一个代码块的同步,但是这个方法不可行,因为一旦进入了方法,这个同步就没太大意义了。不能使用。
-
- 双检锁
优点:双重检查(双检锁)利用将当前实例同步主存的方式,实现只能有一个线程去创建对象,其余线程不会进入同步代码块等待,直接会在第一次检查被过滤掉,推荐使用。class Singleton { // 私有化构造器 private Singleton() { } // 创建当前类对象 private static volatile Singleton instance; // 这里一定要加volatile关键字 如果不知道这个是干嘛的 可以参考本人写的JUC文章 有介绍 // 返回当前类实例 public static synchronized Singleton getInstance() { // 如果当前实例为空 则创建 否则返回 if (instance==null){ synchronized (Singleton.class){ if (instance==null){ instance = new Singleton(); System.out.println("创建了一个实例"); } } }else{ System.out.println("不为空"); } return instance; } }
- 静态内部类
优点:SingletonInstance类并不会在Singleton被加载而连带加载,调用Singleton Instance类的时候 类加载机制是线程安全的,所以实现了懒加载和线程安全 也是一种比较推荐的方式class Singleton { // 私有化构造器 private Singleton() { } // 静态内部类中创建对象实例 private static class SingletonInstance { private static final Singleton INSTANCE = new Singleton(); } // 返回当前类实例 public static Singleton getInstance() { return SingletonInstance.INSTANCE; } }
- 枚举
优点:使用jdk1.5提供的枚举机制,可以很轻松的实现单例模式,不仅可以避免多线程问题,而且还可以避免反序列化创建新的对象,Effective Java中提倡使用该做法enum Singleton{ INSTANCE; }
总结:懒汉式,同步方法饿汉式,双检锁,静态内部类,枚举都可以解决线程安全问题,其中同步代码块饿汉式,无法解决多线程的问题。
根据本文中的分析可以得知 单例模式的好坏 取决于三种情况 1. 能否解决多线程安全 2.是否懒加载 3. 是否会导致多次重复加载同步方法影响效率。 综上所述,推荐使用静态内部类,双检锁,枚举这三种方式实现单例模式!
本系列文章参考韩顺平老师的设计模式编写,保证不用做盈利行为,仅作为个人笔记分享。
全面发展,一专多能!