常用设计模式之单例模式

来源:互联网 发布:重庆地区网络推广 编辑:程序博客网 时间:2024/05/20 02:27

1.问题描述

在一个系统运行期间,某个类只需要一个实例运行就可以,该如何实现呢?

2.模式定义

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

3.解决思路

控制一个类只创建一个实例,首先就是要把类创建的权限收回,让类负责自己实例的创建,然后再提供外部访问实例的方法。代码如下所示:

public class SingleInstance{        private static SingleInstance instance;        private SingleInstance(){}        private static SingleInstance getInstance(){                  if(instance == null)                        instance = new SingleInstance();                  return instance;        }        ......}
4.线程安全

上述代码创建了可延迟初始化的单例,然而在高并发的环境中,getInstance()方法返回了多个指向不同的实例。以下给出两个线程并发访问getInstance()方法时的一种情况:

Thread1

if(instance == null)      instance = new SingleInstance();return instance;
Thread2
if(instance == null)     instance = new SingleInstance();return instance;
如果这两个线程按照上述步骤执行,当还没有创建单例对象,Thread1和Thread2都会进入创建单例实例的代码块分别创建实例。此时,Thread1创建了一个实例对象,但是Thread2此刻已无法知道,于是继续创建一个新的单例对象,导致这两个线程持有的实例并非同一个。

为解决这个问题,给此方法添加synchronized关键字,代码如下:

public class ThreadSafeSingleInstance{         public static synchronized ThreadSafeSingleInstance getInstance(){                   if(instance == null)                             instance = new ThreadSafeSingleInstance();                   return instance;}}
虽然通过synchronized实现了多线程的安全访问,但是在多线程高并发的情况下,会使得性能大不如前。经分析,不难发现,使用synchronized对整个getInstance()方法进行同步是没有必要的,我们只要保证实例化这个对象的那段逻辑被一个线程执行就可以了,而返回引用的那段代码是没必要同步的。实现如下:

public DoubleCheckSingleInstance{         private volatile static DoubleCheckSingleInstance instance = nul;         //构造函数         public static DoubleCheckSingleInstance getInstance(){                   if(instance == null){                           synchronized(DoubleCheckSingleInstance.class){                                   if(instance == null)                                       instance = new DoubleCheckSingleInstance();                           }                   }                 return instance;         }}
代码注解:

在getInstance()方法里,首先判断此实例是否已经被创建了,如果还没有创建,使用synchronized同步实例化代码块。在同步代码块里,还需要再次检查是否已经创建了此类的实例,如没有第二次检查,当有两个线程同时进入该方法,同样会出现生成两个实例对象。

5.场景假设

假设有两个线程,Thread1和Thread2,它们执行以下步骤:

(1)Thread1发现instance没有被实例化,它获得锁,并去实例化此对象,JVM容许在没有完全实例化完成时,instance变量就指向此实例,因为这些步骤可以是次序颠倒的,此时instance == null为false

(2)在初始化完成之前,Thread2进入此方法,发现instance已经不为null了,Thread2便认为该实例已经初始化完成了,使用这个未完成初始化的实例对象,则很可能引起系统的崩溃。

如要设计使用线程安全的延迟的单例初始化,可使用Initialization on demand holder模式。代码如下:

public class LazyLoadedSingleInstance{         private LazyLoadedSingleInstance(){}         private static class LazyHolder{                    private static final LazyLoadedSingleInstance singletonInstance = new LazyLoadedSingleInstance();         }         public static LazyLoadedSingleInstance getInstance(){                 return LazyHolder.singletonInstance;         }}
代码注解:

当JVM加载LazyLoadedSingleInstance类时,由于该类没有static属性,所以加完完成后便即可返回。只有第一次调用getInstance()方法时,JVM才会加载LazyHolder类,由于它包含一个static属性singletonInstance,所以会首先初始化这个变量,此过程并不会出现并发问题,这样即实现了一个既保证线程安全又支持延迟加载的单例模式。

6.应用场景

如果要保证系统里一个类最多只能存在一个实例时,我们就需要单例模式。例如缓存池、数据库连接池、线程池,一些应用服务实例等。

原创粉丝点击