zoukankan      html  css  js  c++  java
  • Java设计模式之单例模式

    单例模式

     

    在GoF的23种设计模式中,单例模式是比较简单的一种。然而,有时候越是简单的东西越容易出现问题。下面就单例设计模式详细的探讨一下。

    所谓单例模式,简单来说,就是在整个应用中保证只有一个类的实例存在。就像是Java Web中的application,也就是提供了一个全局变量,用处相当广泛,比如保存全局数据,实现全局性的操作等。

    1、最简单的实现 -------饿汉式

    首先,能想到的最简单的实现是,把类的构造函数写成private的,从而保证别的类不能实例化此类。然后在类中返回一个静态示例并返回给调用者。这样,调用者就可以通过这个引用使用这个实例了。

    复制代码
    public class Singleton{
        private static final Singleton singleton = new Singleton();
        
        public static Singleton getInstance(){
            return singleton;
        }
        
        private Singleton(){
        
        }
    }
    复制代码

    如上例,外部使用者如果需要使用SingletonClass的实例,只能通过getInstance()方法,并且它的构造方法是private的,这样就保证了只能有一个对象存在。

    2、性能优化--lazy loaded -----懒汉式

    上面的代码虽然简单,但是有一个问题----无论这个类是否被使用,都会创建一个instance对象。如果这个创建很耗时,比如说链接10000次数据库(夸张一点啦....),并且这个类还不一定会被使用,那么这个创建过程就是无用的,怎么办呢?

    为了解决这个问题,我们想到的新的解决方案:

    复制代码
    public class SingletonClass { 
    
      private static SingletonClass instance = null; 
        
      public static SingletonClass getInstance() { 
        if(instance == null) { 
          instance = new SingletonClass(); 
        } 
        return instance; 
      } 
        
      private SingletonClass() { 
         
      } 
        
    }
    复制代码

    代码的变化有俩处----首先,把 instance 设置为 null ,知道第一次使用的时候判是否为 null 来创建对象。因为创建对象不在声明处,所以那个 final 的修饰必须去掉。

    我们来想象一下这个过程。要使用 SingletonClass ,调用 getInstance()方法,第一次的时候发现instance时null,然后就创建一个对象,返回出去;第二次再使用的时候,因为这个instance事static的,共享一个对象变量的,所以instance的值已经不是null了,因此不会再创建对象,直接将其返回。

    这个过程就称为lazy loaded ,也就是迟加载-----直到使用的时候才经行加载。

    3、同步

    上面的代码很清楚,也很简单。然而就像那句名言:“80%的错误是由20%的代码优化引起的”。单线程下,这段代码没什么问题,可是如果是多线程呢,麻烦就来了,我们来分析一下:

    线程A希望使用SingletonClass,调用getInstance()方法。因为是第一次调用,A就发现instance是null的,于是它开始创建实例,就在这个时候,CPU发生时间片切换,线程B开始执行,它要使用SingletonClass,调用getInstance()方法,同样检测到instance是null——注意,这是在A检测完之后切换的,也就是说A并没有来得及创建对象——因此B开始创建。B创建完成后,切换到A继续执行,因为它已经检测完了,所以A不会再检测一遍,它会直接创建对象。这样,线程A和B各自拥有一个SingletonClass的对象——单例失败!

    解决的办法也很简单,那就是加锁:

    复制代码
    public class SingletonClass{
        private static SingletonClass instance = null;
        public synchronized static SingletonClass getInstance(){
            if(instance == null){
                instance = new SingletonClass();
            }
            return instance;
        }
        private SingletonClass(){
        
        } 
    }
    复制代码

    只要getInstance()加上同步锁,,一个线程必须等待另外一个线程创建完后才能使用这个方法,这就保证了单利的唯一性。

    4、又是性能

    上面的代码又是很清楚也很简单的,然而,往往简单的东西不够理想。这段代码毫无疑问存在性能的问题----synchronized修饰的同步块可是要比一般的代码慢上几倍的!如果存在很多次的getInstance()调用,那性能问题就不得不考虑了?!!!

    让我们来分析一下,究竟是整个方法都必须加锁,还是紧紧其中某一句加锁就足够了?我们为什么要加锁呢?分析一下lazy loaded的那种情形的原因,原因就是检测null的操作和创建对象的操作分离了,导致出现只有加同步锁才能单利的唯一性。

    如果这俩个操作能够原子的进行,那么单利就已经保证了。于是,我们开始修改代码:

    复制代码
    public class SingletonClass{
        private static SingletonClass instance = null;
        public static SingletonClass getInstance(){
            synchronized(SingletonClass.class){
                if(instance == null){
                instance = new SingletonClass();
                }
            }
            return instance;
        }
        private SingletonClass(){
        
        } 
    }
    复制代码

    首先去掉 getInstance() 的操作,然后把同步锁加载到if语句上。但是,这样的修改起不到任何作用:因为每次调用getInstance()的时候必然要经行同步,性能的问题还是存在。如果............我们事先判断一下是不是为null在去同步呢?

    复制代码
    public class SingletonClass{
        private static SingletonClass instance = null;
        public static SingletonClass getInstance(){
            if(instance == null){
                synchronized(SingletonClass.class){
                    instance = new SingletonClass();
                    }
                }   
            return instance;
        }
        private SingletonClass(){
        
        } 
    }
    复制代码

    还有问题吗?首先判断instance是不是为null,如果为null在去进行同步,如果不为null,则直接返回instance对象。

    这就是double---checked----locking 设计实现单利模式。到此为止,一切都很完美。我们用一种很聪明的方式实现了单例模式。

  • 相关阅读:
    hdu 5015
    hdu 2276
    hdu 6197 array array array LIS
    poj 3070
    codefroce 854 A.Fraction
    sql数据库发布、订阅同步方式操作
    python排序(冒泡、直接选择、直接插入等)
    忘记mysql数据库密码时进行修改方法
    Word中图片自动编号且与文中引用的编号对应
    解决电脑本地网络连接显示红叉又可上网问题
  • 原文地址:https://www.cnblogs.com/dhm520/p/8566459.html
Copyright © 2011-2022 走看看