1.饿汉式
package demo5; public class Singleton { private static Singleton instance = new Singleton(); private Singleton(){} public static Singleton newInstance(){ return instance; } }
提供了一个静态实例并返回给调用者
饿汉模式是最简单的一种实现方式,饿汉模式在类加载的时候就对实例进行创建,实例在整个程序周期都存在。优点是只在类加载的时候创建一次实例,不会存在多个线程创建多个实例的情况,避免了多线程同步的问题。缺点是即使这个单例没有用到也会被创建,而且在类加载之后就被创建,内存就被浪费了。
2.懒汉式
public class Singleton{ private static Singleton instance = null; private Singleton(){} public static Singleton newInstance(){ if(null == instance){ instance = new Singleton(); } return instance; } }
懒汉模式是在需要的时候才会被创建,如果已经创建了,那再次调用的时候就不会创建新的对象,而是返回之前创建的对象。但是懒汉模式并没有考虑线程安全问题,在多个线程可能会并发调用它的getInstance()方法,导致创建多个实例,因此需要加锁解决线程同步问题。如下
public class Singleton{ private static Singleton instance = null; private Singleton(){} public static synchronized Singleton newInstance(){ if(null == instance){ instance = new Singleton(); } return instance; } }
3.双重校验锁
上面加锁的懒汉式存在着性能问题,如果多次调用newInstance(),累计下来的性能损耗就比较大。因此就有了如下的双重校验锁。
public class Singleton { private static Singleton instance = null; private Singleton(){} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
再次调用getInstance()只需要直接返回单例对象。所以大部分情况下,调用getInstance()都不会执行到同步代码块。不过还需要考虑一种情况,假如两个线程,一个线程执行了if (instance == null)语句,就会认为单例对象没有创建,此时另一个线程也执行了同样的语句,认为单例对象没有创建,然后两个线程依次执行同步代码块,各自创建了一个单例对象。所以需要在同步代码块中增加if (instance == null)语句。由于JVM底层模型原因,偶尔会出问题,不过JDK1.5及之后版本增加了volatile关键字。volatile的一个语义是禁止指令重排序优化,也就保证了instance变量被赋值的时候对象已经是初始化过的。
public class Singleton { private static volatile Singleton instance = null; private Singleton(){} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } }
4.静态内部类
public class Singleton{ private static class SingletonHolder{ public static Singleton INSTANCE = new Singleton(); } private Singleton(){} public static Singleton newInstance(){ return SingletonHolder.INSTANCE; } }
外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化,这样就不会占用内存。只有当newInstance第一次被调用时,才会初始化INSTANCE,第一次调用会加载SingletonHolder,这样不仅是线程安全的,也能保证单例的唯一性并且延迟了单例的实例化。
5.枚举类
public enum Singleton{ instance; public void whateverMethod(){} }
枚举与普通类一样,可以有字段也可以有方法,并且枚举的实例创建是线程安全的,并且在任何情况都是单例的,但不能延时加载,虽然优点很多,但在主流开发中却很少用到。
懒汉式和饿汉式在使用过程中都有一定的缺陷,在使用过程中一般不建议使用,双重校验锁和静态内部类很好的规避了前面两个的缺点,也是推荐的两种使用方式。