单例模式
Java设计模式 —— 单例模式(Singleton)
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(){}
}
上面的四种实现单例的方法都有共同的缺点:
- 需要额外的工作来实现序列化。
否则每次反序列化一个对象都会创建一个新的实例。 - 可以使用反射器强行调用私有构造器。
要避免这种情况,可以在创建第二个实例时抛出异常。
枚举方法可以很好的解决上面的问题,而且还是线程安全。
总结
本文总结了物种Java实现单例的方法,前两种都不够完美。
双重校验锁和静态内部类可以解决大部分问题,也是平时使用最多的两种方式。
枚举虽然很完美,但是写法让人感觉不习惯。