深入浅出设计模式学习笔记四:单例模式
单例模式,是设计模式中最简单的模式了,其基本定义是指确保一个类只有一个实例,并提供一个全局访问点。
为什么会需要单例模式的存在呢?
因为我们在实际的开发过程中,有些时候只需要一个对象,比如数据库连接池,线程池,缓存,日志对象,硬件设备的驱动程序的对象等,如果制造多个实例的话,会导致许多问题的产生,比如:程序的行为异常,资源过多浪费,结果不一致等。
单例模式的特点:
1、单例类在任何时刻都只有一个对象;
2、单例对象只能由单例类自己创建;
单例模式的类图:
单例模式常见的几种实现如下所示:
一、经典的单例模式的实现(懒汉式 线程不安全)
1 public class Singleton { 2 3 private static Singleton uniqueInstance; 4 5 private Singleton(){} 6 7 public static Singleton getInstance(){ 8 9 if (uniqueInstance == null) { 10 11 uniqueInstance = new Singleton(); 12 } 13 return uniqueInstance; 14 } 15 16 }
Singleton类拥有一个私有的静态变量uniqueInstance,私有的构造函数Singleton(),以及一个公共静态方法getInstance(),通过将构造函数设置私有的防止类在外部进行实例化(在这里我们忽略java的反射机制,因为java的反射机制能够实例化任意访问权限的构造函数,java的所有单例实现都会失效),Singleton的实例只能通过静态方法getInstance()获取。
但是以上的单例的实现是线程不安全的,如果此时有两个以上的线程调用getInstance()生成Singleton的实例时,会返回多个实例,违反了单例模式的设计初衷。
二、懒汉式 线程安全的
为了解决第一个实现线程不安全的问题,将getInstance()方法变成同步,这样迫使每个线程在进入这个方法之前,需要先等候别的线程离开该方法,即不会有两个线程同时进入该方法。
1 public static synchronized Singleton getInstance(){ 2 3 if (uniqueInstance == null) { //1 4 5 uniqueInstance = new Singleton(); //2 6 } 7 return uniqueInstance; //3 8 }
但是同步会降低性能,而且,通过分析代码可以知道,只有在第一次执行该方法的时候,才会需要同步,因为真正需要同步的,只有//2处的代码,没有必要对后面的代码使用同步,一旦设置好uniqueInstance变量,就不再需要同步该方法了,但是由于该方法是synchronized的,除却第一次调用的时候,以后每次调用该方法的时候,同步都是一种累赘。
三、双重检查锁
利用双重检查锁,首先检查实例是否已经创建,如果未创建,才进行同步,这样,只有在第一次调用该方法会同步,保证了代码的性能和效率。
但是只适用于JDK1.5及以上的版本
1 public class Singleton{ 2 3 // volatile关键词确保当uniqueInstance被初始化为Singleton1的实例时,多个线程可以正确处理uniqueInstance 4 private volatile static Singleton uniqueInstance; 5 6 private Singleton(){} 7 8 public static Singleton getInstance(){ 9 //判断实例是否存在,不存在的话进入同步代码块 10 if (uniqueInstance == null) { 11 //只有在第一次才会执行该代码块 12 synchronized (Singleton.class) { 13 //进入同步代码块后会再检查一次实例是否存在,如果不存在,才会创建实例 14 if (uniqueInstance == null) { 15 16 uniqueInstance = new Singleton(); 17 } 18 } 19 } 20 return uniqueInstance; 21 } 22 23 }
四、饿汉式
基于类加载机制(稍后会重开一篇详细介绍)保证了线程安全,在类装载时就实例化。
1 public class Singleton{ 2 3 private static Singleton uniqueInstance = new Singleton(); 4 5 private Singleton(){}; 6 7 public static final Singleton getInstance() { 8 return uniqueInstance; 9 } 10 }
五、静态内部类
线程安全的,跟第四种方法的差别在于,第四种方法是只要Singleton被装载,就会实例化uniqueInstance,没有达到 lazy loading的效果,而这种方法,只有在调用getInstance()方法时,才会加载SingletonHolder类,进而实例化instance。
1 public class Singleton { 2 //inner static class 3 private static class SingletonHolder { 4 private static final Singleton INSTANCE = new Singleton(); 5 } 6 //private and no-reference constructor method 7 private Singleton(){}; 8 //public static method getInstance 9 public static final Singleton getInstance() { 10 return SingletonHolder.INSTANCE; 11 } 12 }
饿汉式和懒汉式的区别:
1、顾名思义,饿汉式是在Singleton类被装载时,就会实例化uniqueInstance,而懒汉式是在调用getInstance()方法时,才会去实例化uniqueInstance;
2、饿汉式是线程安全的,懒汉式本身是线程不安全的,可通过二,三,五三种方法使其变成线程安全的;
3、饿汉式是用“空间换时间”,而懒汉式是用“时间换空间”。
以静态内部类为例,实现一个使用单例模式的例子:
1 class Singleton { 2 3 private static class SingletonHolder { 4 private static final Singleton INSTANCE = new Singleton(); 5 } 6 private String nameString = ""; 7 8 private Singleton(){}; 9 10 public String getNameString() { 11 return nameString; 12 } 13 14 public void setNameString(String nameString) { 15 this.nameString = nameString; 16 } 17 18 public static final Singleton getInstance() { 19 return SingletonHolder.INSTANCE; 20 } 21 22 public void printInfo() { 23 System.out.println("the name is " + nameString ); 24 } 25 } 26 27 public class TestSingleton{ 28 29 public static void main(String[] args) { 30 31 Singleton test1 = Singleton.getInstance(); 32 test1.setNameString("abc"); 33 Singleton test2 = Singleton.getInstance(); 34 test2.setNameString("123"); 35 36 test1.printInfo(); 37 test2.printInfo(); 38 39 if (test1 == test2) { 40 System.out.println("the same singleton"); 41 }else { 42 System.out.println("the different singleton"); 43 } 44 } 45 }
运行结果:
单例模式的优缺点:
优点:
1、确保所有的对象访问的都是同一个实例;
2、由于系统中只存在一个实例,因此可以节约资源;
3、避免对共享资源的多重占用;
4、简化复杂环境下的配置管理;
缺点:
1、单例类没有抽象层,因此不利于扩展;
2、不适用于变化的对象,如果同一个类的实例要在不同的场景发生变化,单例会引起数据错误,不能保存彼此的状态;
3、职责过重,在一定程度上违背了“单一职责原则”;