Java中单例模式是一种广泛使用的设计模式。

单例模式的作用

: 单例模式的主要作用是保证在Java程序中,某个类只有一个实例存着。
一些管理器和控制器经常被设计成单例模式。

  • 避免实例对象的重复创建。减少每次创建对象的时间开销,节约内存空间。
  • 避免操作多个实例导致的逻辑错误。
  • 全局统一管理控制一个对象。

饿汉模式

public class Singleton{  
    private static Singleton instance = new Singleton();  
    private Singleton(){}  
    public static Singleton getInstance(){  
        return instance;  
    }  
}

优点

饿汉模式的优点是只在类加载的时候创建一次实例,不会存在多个线程创建多个实例的情况,避免了多线程同步的问题。

缺点

即使没有单例没有用到也会被创建,而且在类加载之后就被创建,浪费了内存。

适用情况

这种实现方式适合单例占用内存比较小,在初始化时就会被用到的情况。

懒汉模式

public class Singleton{  
    private static Singleton instance = null;  
    private Singleton(){}  
    public static Singleton getInstance(){  
        if(null == instance){  
            instance = new Singleton();  
        }  
        return instance;  
    }  
} 

特点

懒汉模式的特点就是延迟加载。单例只有在需要的时候才去创建。
如果单例已经创建,再次调用获取接口将直接返回之前创建的对象。

适用情况

创建单例消耗的资源较多

缺点

懒汉模式没有考虑线程安全问题,因此需要加锁解决线程同步问题。

public class Singleton{  
    private static Singleton instance = null;  
    private Singleton(){}  
    public static synchronized Singleton getInstance(){  
        if(null == instance){  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

双重校验锁

加锁的懒汉模式虽然解决了线程并发问题,又实现了延迟加载,但是它存在性能问题。
synchronized修饰的同步方法比一般方法要慢很多,多次调用getInstance(),累积的性能损耗就比较大。因此有了双重校验锁。

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;  
    }  
} 

虽然上面的代码提高了程序性能,但也并不是万无一失。
由于Java中指令重排优化的存在,会导致初始化Singleton和将对象地址赋给instance字段的顺序是不确定的。

所谓指令重排优化是指在不改变原语义的情况下,通过调整指令的执行顺序让程序运行的更快。
JVM中并没有规定编译器优化相关的内容,也就是说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;  
    }  
}  

静态内部类

public class Singleton{  
    private static class SingletonHolder{  
        public static Singleton instance = new Singleton();  
    }  
    private Singleton(){}  
    public static Singleton getInstance(){  
        return SingletonHolder.instance;  
    }  
} 

这种方式既实现了饿汉模式的类加载机制,也实现了懒汉模式的延迟加载。
而且不存在多线程并发问题。

枚举

public enum Singleton{  
    instance;  
    public void whateverMethod(){}      
} 

上面的四种实现单例的方法都有共同的缺点:

  1. 需要额外的工作来实现序列化。否则每次反序列化一个对象都会创建一个新的实例。
  2. 可以使用反射器强行调用私有构造器。要避免这种情况,可以在创建第二个实例时抛出异常。

总结

本文总结了物种Java实现单例的方法,前两种都不够完美。
双重校验锁和静态内部类可以解决大部分问题,也是平时使用最多的两种方式。
枚举虽然很完美,但是写法让人感觉不习惯。