什么是单例模式?
所谓的单例所及模式,就是采取一定的方法保证整个软件系统中,对某个类只能存在一个对象实例,且该类对外只提供一个获取其对象实例的方法(静态方法)。单例模式很重要的一点就是一定要私有化构造器,防止通过new来实例化多个对象。
饿汉式
饿汉式名字的来源大概就是该静态变量会再类装载的时候就完成了实例化,这样就如同装载一个类如同在做大饼,大饼刚做好就立马将它吃掉(实例化对象),故而因此命名,具体实现方法有两种,一种是通过静态变量来实例化该对象,一种是在静态代码块实例化对象,二者本质上没有什么区别。
静态变量:
class Singleton01{
private static final Singleton01 SINGLETON = new Singleton01();
private Singleton01(){}//私有化构造器
public static Singleton01 getInstance(){
return SINGLETON;
}
}
静态代码块:
class Singleton01{
private Singleton01(){}
private static Singleton01 SINGLETON;
static {
SINGLETON = new Singleton01();
}
public static Singleton01 getInstance(){
return SINGLETON;
}
}
上述两种饿汉式的写法都避免了线程同步的问题,但是如果类装载的时候没有使用该实例的话,就会造成内存的浪费,没有实现懒加载。
懒汉式
懒汉式和饿汉式的区别在于,懒汉很懒,只有在肚子实在饿的不行的时候才会去取饼吃(实例化),也就是说在做完大饼的时候(类装载的时候),该单例并不会主动去加载,只有真正要用到了才会去加载,我们称此为懒加载。
异步懒汉式
class Singleton01{
private Singleton01(){}
private static Singleton01 SINGLETON;
public static Singleton01 getInstance(){
if(SINGLETON == null){
SINGLETON = new Singleton01();
}
return SINGLETON;
}
}
该代码有一个致命的漏洞,便是在多线程的情况下,非常有可能出现实例化多个对象的情况。因为代码是异步的,即当有两个即以上的线程并发进行 if(SINGLEON == null) 判断时,得到的结果都为true,结果执行了多次包含SINGLETON = new singleton01();的代码块,所以有多个实例被创建,不能算是单例模式了。
同步方法懒汉式
其实该方法就是在上一个异步懒汉式的getInstance方法用synchronized来修饰,从而避免出现并发导致的线程安全问题,但是这种方法会大大降低效率。
同步代码块
class Singleton01{
private Singleton01(){}
private static Singleton01 SINGLETON;
public static Singleton01 getInstance(){
if(SINGLETON == null){
synchronized (Singleton01.class){//此处用Singleton01.class作为锁对象是因为它就是一个单例。
SINGLETON = new Singleton01();
}
}
return SINGLETON;
}
}
然而仔细推敲上面这种写法还是会和第一种懒汉式一样遇到线程安全问题,因为只要在判断对象是否为空的时候,就会出现并发问题,而加在其内的同步代码块只是延缓了执行的过程,却没有真正意义上阻止后面等待的线程去实例化新对象,所以以上懒汉式的三种方式都有明显的缺陷,不推荐使用。
双重检查法
其实上面一种方法已经意识到了问题的关键之所在,只是又犯了同样的错误(new 实例之前应当判断是否为空),所以我们大可再用同样的方式来检查一点,这样就可以完美避免线程安全问题,代码如下
class Singleton01{
private Singleton01(){}
private static Singleton01 SINGLETON;
public static Singleton01 getInstance(){
if(SINGLETON == null){
synchronized (Singleton01.class){
if(SINGLETON == null)
SINGLETON = new Singleton01();
}
}
return SINGLETON;
}
}
静态内部类法
class Singleton01{
private Singleton01(){
if(SingletonInstance.INSTANCE!=null){
throw new IllegalStateException();//防止反射入侵外部类
}
}
private static class SingletonInstance{
private static final Singleton01 INSTANCE = new Singleton01();
}
public static Singleton01 getInstance(){
return SingletonInstance.INSTANCE;
}
}
这种写法用JVM的类加载机制来保证数据的同步。只要我们不去使用该内部类,JVM就不会去加载该内部类(懒加载),而且还能保证只在装载的时候实例化一次,可以保证线程的安全。兼顾了懒汉式和饿汉式的优点,缺点就是多创建了一个类,增强了耦合性,而且其生命周期只能通过JVM去控制,不能手动destroy了,但是这种方法还是很推荐使用的。
枚举法
enum Singleton01{
INSTANCE;
public static void sayHello(){
System.out.println("hello,world");
}
}
运用枚举创建单例,避免多线程的同时也防止反序列化创建新的对象,是effective Java的作者推荐的一种方法。
总结
单例模式的存在就是为了使得当前系统的内存中仅有一个对象,节省了系统资源也避免了许多不必要的开销,提供了系统的性能。当想获取一个单例的时候应该想着用它提供的方法而非使用new。
单例模式常用场景:
- 需要频繁创建和销毁对象
- 创建该对象需要消耗大量的资源(即对象非轻量级),但是该对象却又要经常用到
- 工具类对象或者数据库访问或文件访问对象(比如数据源与session工厂等)