深入浅出设计模式学习笔记四:单例模式

单例模式,是设计模式中最简单的模式了,其基本定义是指确保一个类只有一个实例,并提供一个全局访问点。

为什么会需要单例模式的存在呢?

因为我们在实际的开发过程中,有些时候只需要一个对象,比如数据库连接池,线程池,缓存,日志对象,硬件设备的驱动程序的对象等,如果制造多个实例的话,会导致许多问题的产生,比如:程序的行为异常,资源过多浪费,结果不一致等。

单例模式的特点:

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、职责过重,在一定程度上违背了“单一职责原则”;

原文地址:https://www.cnblogs.com/miaowu1314/p/5559076.html