单例模式
单例模式是最常用的设计模式,具有容易理解、使用简便的特点,它主要涉及一个单一的类,该类负责创建自己的对象,同时确保这个类只有单个对象被创建。Spring管理的bean默认都是单例的。
单例模式的实现非常简单,只由单个类组成。为了确保单例实现的唯一性,单例的构造方法都要被声明为private,再通过static方法实现全局访问获得该单例实例。
单例模式的实现有以下几种写法:
- 饿汉式写法
- 懒汉式写法(线程不安全)
- 双重校验的同步锁式写法
- 静态内部类写法
- 枚举实现写法
-
饿汉式写法
//饿汉式写法(静态常量) class Singleton{ //本类内部创建对象实例 private final static Singleton SINGLETON = new Singleton(); //构造方法私有化,外面不能new了 private Singleton(){} //提供一个公有的静态方法,返回对象实例 public static Singleton getInstance(){ return SINGLETON; } }
//饿汉式写法(静态代码块) class Singleton2{ //本类内部创建对象实例 private final static Singleton2 SINGLETON ; static { SINGLETON = new Singleton2(); } //构造方法私有化,外面不能new了 private Singleton2(){} //提供一个公有的静态方法,返回对象实例 public static Singleton2 getInstance(){ return SINGLETON; } }
饿汉式写法是线程安全的,不管是不是在多线程环境下,它都只会在类加载的时候实例化一次 。但是,如果整个程序中你没有用到这个实例,就会造成这部分内存的浪费,所以,就有了下面的懒汉式写法,它只会在你需要用到这个实例的情况下才会进行实例化一次。
-
懒汉式写法
//懒汉式写法(线程不安全),开发不推荐使用 class Singleton{ //本类内部声明对象实例 private static Singleton SINGLETON ; //构造方法私有化,外面不能new了 private Singleton(){} //提供一个公有的静态方法,当使用该方法的时,才会去创建对象实例 public static Singleton getInstance(){ if (SINGLETON == null){ SINGLETON = new Singleton(); } return SINGLETON; } }
懒汉式写法虽然消除了内存浪费的问题,但却是线程不安全的,只能在单线程环境下使用,如果在多线程环境下,一个线程通过了if (SINGLETON == null)语句的判断,但是还来得及实例化对象,这时候又有一个线程通过了if (SINGLETON == null)语句的判断,这样就不能保证实现单例模式了。
还有一种写法,为了解决懒汉式写法的线程不安全问题,引入了同步代码块来解决。
//懒汉式写法(线程安全,同步代码块) 效率太低,开发不推荐使用 class Singleton2{ //本类内部创建对象实例 private static Singleton2 SINGLETON ; //构造方法私有化,外面不能new了 private Singleton2(){} //提供一个公有的静态方法,当使用该方法的时,才会去创建对象实例 //加入同步代码块,解决线程不安全问题 public static synchronized Singleton2 getInstance(){ if (SINGLETON == null){ SINGLETON = new Singleton2(); } return SINGLETON; } }
这样虽然解决了线程不安全的问题,但同步代码块会使开发效率变得很低。这就有了下面的加入了双重校验的同步锁检查写法,即能保证了线程安全,又保证了效率。
-
双重校验的同步锁式写法
//双重检查 class Singleton{ private static volatile Singleton SINGLETON ; private Singleton(){} //加入双重检查代码,解决线程安全问题,同时解决懒加载问题 public static Singleton getInstance(){ if (SINGLETON == null){ synchronized (Singleton.class){ if(SINGLETON == null){ SINGLETON = new Singleton(); } } } return SINGLETON; } }
这样可以保证用来检查实例是否被创建的代码是线程同步的 ,也就是说这段代码块在同一时刻只能被一个线程执行,但是,同步锁只有在实例没有创建的时候才会起作用,如果单例已经被创建成功,那么任何线程都能通过非同步的方式获取当前实例。注意这里进行了两次SINGLETON == null的校验,第一次的SINGLETON == null的判断是为了避免没必要的加锁,第二次的判断是保证在同步代码块里实现单例
-
静态内部类写法
//静态内部类,推荐使用 class Singleton{ private Singleton(){} //使用静态内部类来实例化对象 private static class SingletonInstance{ private static final Singleton SINGLETON = new Singleton(); } public static Singleton getInstance(){ return SingletonInstance.SINGLETON; } }
静态内部类的特点是,在外部类加载的时候并不会立刻加载内部类,只有在调用到getInstance()的时候,使用到了静态内部类,虚拟机才会去加载该内部类。这种写法也能保证线程安全、也能保证实现单例模式、同时也用到了懒加载
-
枚举写法
//枚举实现单例,推荐使用 enum Singleton{ INSTANCE; }
枚举类型是线程安全的,并且只会装载一次,可以利用这个特性来实现单例模式
接着只要通过
Singleton instance = Singleton.INSTANCE;
就可以获得单例模式的实例了