设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。 毫无疑问,设计模式于己于他人于系统都是多赢的;设计模式使代码编制真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
一、什么是单例模式(Singleton Pattern)单例:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式是一种常用的软件设计模式之一,其目的:保证某一个在整个应用中有且只有一个实例(一个类在内存只存在一个对象),即所有指向该类型实例的引用都指向同一块内存空间。
Ensure a class has only one instance, and provide a global point of access to it.(确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例) -- 来自《设计模式之禅》
比如:我们在系统启动时,需要加载一些公共的配置信息,对整个应用程序的整个生命周期中都可见且唯一,这时需要设计成单例模式。如:spring容器,session工厂,缓存,数据库连接池等等。
二、如何保证实例的唯一1)防止外部初始化
2)由类本身进行实例化
3)保证实例化一次
4)对外提供获取实例的方法
5)线程安全
写单例模式的步骤:
1)在类内部里显示的把所有的构造方法都使用private修饰。
2)在类内部构建一个自身对象。
3)通过一个全局静态方法让外界访问到该对象。
三、几种单例模式方式Java 使用单例设计模式的方式有很多种,比如饿汉式,懒汉式,静态内部类式,双重检测锁式以及枚举式等。
1、饿汉式(线程安全)
“因为饿,所以要立即吃饭,刻不容缓”,在定义类的静态私有变量的同时进行实例化。
public class MySingleton {
private static final MySingleton instance = new MySingleton();
private MySingleton() { }
public static MySingleton getInstance() {
return instance;
}
public void doXxx() {
System.out.println("执行方法。。。");
}
}
好处:获取实例速度快 ,类加载即初始化实例,线程安全(static变量会在类装载时初始化,此时也不会涉及多个线程对象访问该对象的问题。虚拟机保证只会装载一次该类,肯定不会发生并发访问的问题)。
缺点:如果只是加载本类,而不是调用getInstance(),甚至永远没有调用,则会造成内存资源的浪费。
即:线程安全,调用效率高 ,但是不能延迟加载
2、懒汉式(线程不安全)
lazy load! 延时加载,懒加载!真正用的时候才加载。
public class MySingleton {
private static MySingleton instance = null;
private MySingleton() { }
public static MySingleton getInstance() {
if(instance == null){
instance = new MySingleton();
}
return instance;
}
}
缺点:可能存在多个访问者同时访问,并构造了多个对象的问题。会造成线程不安全的问题,于是就有了下面加锁的实现。
3、双重检查加锁懒汉式(通常线程安全,低概率不安全)
双重检查加锁:
可以使用“双重检查加锁”的方式来实现,就可以既实现线程安全,又能够使性能不受很大的影响。
什么是“双重检查加锁”机制?
是指:并不是每次进入getInstance()方法都需要同步,而是先不同步,进入方法后,先检查实例是否存在,如果不存在才进行下面的同步块,这是第一重检查,进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。
“双重检查加锁”机制的实现会使用关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
public class MySingleton {
private volatile static MySingleton instance = null;
private MySingleton() { }
public static MySingleton getInstance() {
// 第一重检查
if (instance == null) {
// 同步锁定代码块:此时同步对象是MySingleton.class
synchronized (MySingleton.class){
// 第二重检查
if(instance == null){
instance = new MySingleton();
}
}
}
return instance;
}
}
注意:在java1.4及以前版本中,很多JVM对于volatile关键字的实现的问题,会导致“双重检查加锁”的失败,因此“双重检查加锁”机制只只能用在java5及以上的版本。
提示:由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行效率并不是很高。因此一般建议,没有特别的需要,不要使用。也就是说,由于JVM底层内部模型原因,偶尔会出问题。不建议使用
4、静态内部类式(线程安全)
外部类没有static属性,则不会像饿汉式那样立即加载对象。只有真正调用getInstance()时,才会加载静态内部类。加载类时是线程安全的。instance是 static final类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证了线程安全性兼备了并发高效调用和延迟加载的优势!
《Effective Java》这本书中推荐的一种写法:
public class Singleton {
private Singleton (){}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
写法非常巧妙(简直是神乎其技):
对于内部类SingletonHolder,它是一个饿汉式的单例实现,在SingletonHolder初始化的时候会由ClassLoader来保证同步,使INSTANCE是一个真·单例。
同时,由于SingletonHolder是一个内部类,只在外部类的Singleton的getInstance()中被使用,所以它被加载的时机也就是在getInstance()方法第一次被调用的时候。
它利用了ClassLoader来保证了同步,同时又能让开发者控制类加载的时机。从内部看是一个饿汉式的单例,但是从外部看来,又的确是懒汉式的实现。
好处:线程安全,资源利用率高,可以延时加载
5、枚举式(线程安全)
枚举的思想其实是通过共有的静态 final 与为每个枚举常量导出实例的类,由于没有可访问的构造器,所以不能调用枚举常量的构造方法去生成对应的对象,因此在《Effective Java》 中,枚举类型为类型安全的枚举模式,枚举也被称为单例的泛型化。
《Effective Java》这本书中推荐的一种写法:
public enum SingletonDemo {
INSTANCE;
public void doXxx() {
System.out.println("执行方法。。。");
}
}
// 如果我们想调用它的方法时,仅需要以下操作:
public class DesignDemo {
public static void main(String[] args) {
SingletonDemo instance1 = SingletonDemo.INSTANCE;
SingletonDemo instance2 = SingletonDemo.INSTANCE;
System.out.println(instance1 == instance2); // true
SingletonDemo.INSTANCE.doXxx(); // 执行方法。。。
}
}
写法非常巧妙,解决了以下三个问题:
(1)自由串行化。
(2)保证只有一个实例。
(3)线程安全。
优点:线程安全、调用效率高,并且可以天然的防止反射和反序列化漏洞!
缺点:它不能延时加载,在需要继承的场景(枚举类默认已隐式继承Enum类),它就不适用了。
四、该如何选择这些方式?单例对象占用资源少,不需要延时加载:枚举式 好于 饿汉式
单例对象占用资源大,需要延时加载:静态内部类式 好于 懒汉式
ends ~