zoukankan      html  css  js  c++  java
  • JAVA设计模式中的单例模式

    单例模式

    单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
    这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
    注意:
    1、单例类只能有一个实例。
    2、单例类必须自己创建自己的唯一实例。
    3、单例类必须给所有其他对象提供这一实例。

    介绍

    意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
    主要解决:一个全局使用的类频繁地创建与销毁。
    何时使用:当您想控制实例数目,节省系统资源的时候。
    如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
    关键代码:构造函数是私有的。

    应用实例:

    1、一个党只能有一个主席。

    2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。

    3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。

    优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。 2、避免对资源的多重占用(比如写文件操作)。

    缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
    使用场景: 1、要求生产唯一序列号。 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

    注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。

    一、 懒汉式单例

     1     public class Singleton {  
     2         private Singleton() {}  
     3         private static Singleton single=null;  
     4        
     5         public static Singleton getInstance() {  
     6              if (single == null) {    
     7                  single = new Singleton();  
     8              }
     9 
    10             return single;  
    11         }  
    12     }  
    View Code

    Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。
    (事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)

    但是以上懒汉式单例的实现没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton实例,要实现线程安全,有以下三种方式,都是对getInstance这个方法改造,保证了懒汉式单例的线程安全,如果你第一次接触单例模式,对线程安全不是很了解,可以先跳过下面这三小条,去看饿汉式单例,等看完后面再回头考虑线程安全的问题:

    1、在getInstance方法上加同步

    1     public static synchronized Singleton getInstance() {  
    2          if (single == null) {    
    3              single = new Singleton();  
    4          }    
    5 
    6         return single;  
    7     }  
    View Code

    2. 双重检查锁定

     1     public static Singleton getInstance() {  
     2         if (singleton == null) {    
     3             synchronized (Singleton.class) {    
     4                if (singleton == null) {    
     5                   singleton = new Singleton();   
     6                }    
     7             }    
     8         } 
     9    
    10         return singleton;   
    11     }  
    View Code

    3. 静态内部类

     1     public class Singleton {    
     2         private static class LazyHolder {    
     3            private static final Singleton INSTANCE = new Singleton();    
     4         }
     5 
     6         private Singleton (){};
     7     
     8         public static final Singleton getInstance() {    
     9            return LazyHolder.INSTANCE;    
    10         }    
    11     }    
    View Code

    二、饿汉式单例

    1     public class Singleton1 {  
    2         private Singleton1() {}  
    3         private static final Singleton1 single = new Singleton1();  
    4 
    5         public static Singleton1 getInstance() {  
    6             return single;  
    7         }  
    8     }  
    View Code

    饿汉式在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以天生是线程安全的。

    三、 饿汉式和懒汉式区别

    从名字上来说,饿汉和懒汉,

    饿汉就是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例是已经存在的了,

    而懒汉比较懒,只有当调用getInstance的时候,才回去初始化这个单例。

    另外从以下两点再区分以下这两种方式:


    1、线程安全:

    饿汉式天生就是线程安全的,可以直接用于多线程而不会出现问题,

    懒汉式本身是非线程安全的,为了实现线程安全有几种写法,分别是上面的1、2、3,这三种实现在资源加载和性能方面有些区别。

    2、资源加载和性能:

    饿汉式在类创建的同时就实例化一个静态对象出来,不管之后会不会使用这个单例,都会占据一定的内存,但是相应的,在第一次调用时速度也会更快,因为其资源已经初始化完成,

    而懒汉式顾名思义,会延迟加载,在第一次使用该单例的时候才会实例化对象出来,第一次调用时要做初始化,如果要做的工作比较多,性能上会有些延迟,之后就和饿汉式一样了。

    至于1、2、3这三种实现又有些区别,

    第1种,在方法调用上加了同步,虽然线程安全了,但是每次都要同步,会影响性能,毕竟99%的情况下是不需要同步的,

    第2种,在getInstance中做了两次null检查,确保了只有第一次调用单例的时候才会做同步,这样也是线程安全的,同时避免了每次都同步的性能损耗

    第3种,利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗,所以一般我倾向于使用这一种。

    什么是线程安全?

    如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

    或者说:一个类或者程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,那就是线程安全的。

    测试代码:

     1 public class Singleton {
     2 
     3     String name = null;
     4     
     5     private Singleton (){};
     6     
     7     private  static Singleton instance  = new Singleton();
     8     
     9     private static Singleton getInstance(){
    10         return instance;
    11     }
    12     
    13     public String getName() {
    14     return name;
    15     }
    16 
    17     public void setName(String name) {
    18     this.name = name;
    19     }
    20     
    21     public void printName(){
    22         System.out.println("Name is: " + name);
    23     }
    24 
    25     public static void main(String[] args) {
    26 
    27     Singleton s1 = Singleton.getInstance();
    28     s1.setName("abcd");
    29     Singleton s2 = Singleton.getInstance();
    30     s2.setName("efgh");
    31             
    32     s1.printName();
    33     s2.printName();
    34             
    35     System.out.println(s1);
    36     System.out.println(s2);
    37             
    38     }
    39 
    40 }
    41     
    View Code
  • 相关阅读:
    JDOJ 2197: 校门外的树
    简单线段树知识点详解
    求GCD(最大公约数)的两种方式
    USACO Buying Feed, II
    USACO Dueling GPS's
    USACO Milking Cows
    NOIP 2014 比例简化
    USACO Clumsy Cows
    JDOJ 1140: 完数
    NOIP 2008 火柴棒等式
  • 原文地址:https://www.cnblogs.com/fengze/p/6771640.html
Copyright © 2011-2022 走看看