zoukankan      html  css  js  c++  java
  • 深入浅出设计模式学习笔记四:单例模式

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

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

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

    单例模式的特点:

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

  • 相关阅读:
    博客园特效页脚保存
    go channel
    goland 注册
    mac安装go环境
    go 结构体与方法
    gin教程
    hihocoder234周 计算不包含黑点的矩形个数
    参考文献的正确姿势
    vscode用法
    使用extract-text-webpack-plugin提取css文件
  • 原文地址:https://www.cnblogs.com/miaowu1314/p/5559076.html
Copyright © 2011-2022 走看看