• (转载)单例模式的几种应用


    转自:http://michaelye1988.iteye.com/blog/1714953

    什么是单例模式:

    确保一个类只有一个实例,并提供一个全局访问点。

    注意:使用起来类似静态方法,但是它不是静态方法,而是类。需要new关键字来实例化。

    单例模式的基本使用方式可以概括为3个步骤:

    1.创建一个private静态变量;

    2.创建一个private空构造器;

    3.创建一个public静态访问点,用来将唯一实例返回给外部调用者。

    基本使用方式:

    方法1:

    Java代码  收藏代码
    1. public class SingletonCommon {  
    2.   
    3.     private static SingletonCommon uniqueInstance;//步骤一  
    4.       
    5.     private SingletonCommon(){}//步骤二  
    6.       
    7.     public static SingletonCommon getInstance()//步骤三  
    8.     {  
    9.         if(uniqueInstance == null)  
    10.         {  
    11.             uniqueInstance = new SingletonCommon();  
    12.         }  
    13.         return uniqueInstance;  
    14.     }  
    15. }  

    虽然看上去简单。但是这个类还存在问题。

    我们的程序基本都是多线程的。就存在这样的可能:多个线程同时去调用这个实例,假设这个时候uniqueInstance == null,而线程A执行到if(uniqueInstance == null)这句语句,这时候系统线程调度,线程A交出cpu。转而执行B线程中的代码,而在B线程中也有可能访问这个类,那么它访问的时候,发现uniqueInstance == null,自然就创建一个实例。等到cpu的使用权回到A的时候,A从刚才停住的地方开始执行,也创建了一个实例!所以这个时候问题就出现了。

    那么怎么解决这个问题呢?

    方法:2:

    使用synchronized关键字

    通过这个关键字可以迫使线程在进入这个方法前,要等别的线程离开这个方法,这样就可以不会有两个线程同时访问这个类了,我们的问题就解决了。而且使用起来很简单,仅仅多了一个synchronized关键字而已。

    Java代码  收藏代码
    1. public class SingletonSynchronized {  
    2.   
    3.     private static SingletonSynchronized uniqueInstance;  
    4.       
    5.     private SingletonSynchronized(){}  
    6.       
    7.     public static synchronized SingletonSynchronized getInstance()  
    8.     {  
    9.         if(uniqueInstance == null)  
    10.         {  
    11.             uniqueInstance = new SingletonSynchronized();  
    12.         }  
    13.         return uniqueInstance;  
    14.     }  
    15. }  

    但是这样也存在一个问题:效率。

    每次调用getInstance()的时候都要进行同步,会造成负担。

    那么怎么办才能提高效率呢?

    方法3:

    使用 双重检查加锁

    利用双重检查加锁,首先检查是否已经创建了实例,如果还没有创建,才进行同步

    这样一来就只有第一次会同步。这样就可以提高效率。

    这里需要引入volatile关键字,这个关键字确保当uniqueInstance变量被初始化成SingletonDoubleLocking实例时,

    多个线程正确地处理uniqueInstance变量。

    Java代码  收藏代码
    1. public class SingletonDoubleLocking {  
    2.   
    3.     private volatile static SingletonDoubleLocking uniqueInstance;  
    4.       
    5.     private SingletonDoubleLocking(){}  
    6.       
    7.     public static SingletonDoubleLocking getInstance()  
    8.     {  
    9.         synchronized (SingletonDoubleLocking.class)   
    10.         {  
    11.             if(uniqueInstance == null)  
    12.             {  
    13.                 uniqueInstance = new SingletonDoubleLocking();  
    14.             }  
    15.         }  
    16.             return uniqueInstance;  
    17.     }  
    18. }  

    这样做似乎已经无懈可击了。但是还有问题存在!

    问题是:双重检查加锁不适用于1.4及更早版本的Java

    那还有别的方法吗?

    我们知道前面的做法主要都是为了围绕着如何避免产生多个单例。

    那能不能在调用getInstance()之前就生成实例呢?这样不就避免了所有的问题了吗?

    方法4:

    急切创建实例

    在虚拟机加载这个类的时候就马上创建单例,这样就确保了在任何线程访问这个变量的时候,该实例都已经存在了。

    Java代码  收藏代码
    1. public class SingletonEagerly {  
    2.   
    3.     private static SingletonEagerly uniqueInstance = new SingletonEagerly();//虚拟机加载这个类的时候,这个类就已经被创建  
    4.       
    5.     private SingletonEagerly(){}  
    6.       
    7.     public static SingletonEagerly getInstance()  
    8.     {  
    9.         return uniqueInstance;//不需要判断是否为null,直接返回就可以了  
    10.     }  
    11. }  

    总结:

    方法1:最普通的单例,建议不要这样使用,以免出问题。

    方法2:使用synchronized关键字,使得每个线程进入这个方法之前,要等候别的线程离开这个方法才进入。对程序性能要求不高的的情况下可以使用,仅仅比普通的单例模式多了一个关键字而已。

    方法3:双重检查加锁法,使用volatile关键字,它确保了变量被初始化为实例时,多个线程能够正确地处理变量。这是个不错的选择,但是要注意不能用于java1.4及之前的java版本。在Android中是可以选择这样做的,因为一般我们选择的jdk都是1.5及以上的。

    方法4:急切创建实例法,如果这个类的不大,负担不重,那么可以考虑直接在JVM加载的这个类的时候,直接就生成实例。

     还有一种经常能够见到的单例设计:

    public  class SingletonEagerly {
    	private static SingletonEagerly intence = null;
    	private SingletonEagerly() {
    	}
    	public static SingletonEagerly getInstance(){
    		if (intence == null) {
    			synchronized (SingletonEagerly.class) {
    				if (intence == null) {
    					return new SingletonEagerly();
    				}
    				return intence;
    			}
    		}
    		return intence;
    	} 
    }
    

      

    我将上面4种方法,写成一个Android上可以运行的demo,demo中有两个Activity,在这两个Activity中通过单例的调用方式都将单例的的地址打印出来。我们可以看见,它们在内存中的物理地址一样,则表明它们是同一个对象。

    如图所示:

    有兴趣的同学可以下载来看看:

    https://github.com/michaelye/SingletonPattern

  • 相关阅读:
    Qt: 自动调整到最合适的大小(不是很明白)
    Qt: 读写二进制文件(写对象, 原始数据等)
    Qt: 把内容写进字符串中与C++很相似(使用QTextStream包装QString)
    2008技术内幕:T-SQL语言基础
    bootstrap + angularjs + seajs构建Web Form前端2
    SignalR 2.0 系列: SignalR简介
    Amazon前技术副总裁解剖完美技术面试
    MongoDB数据文件内部结构
    SQL Server三种表连接原理
    了解mongoDB存储结构
  • 原文地址:https://www.cnblogs.com/fengchuxiaodai/p/6020433.html
Copyright © 2020-2023  润新知