首页 > 代码库 > 04 单例模式

04 单例模式

单例模式确保某一个类只有一个实例,自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。

技术分享

                                                      单例模式的UML类图

从图中我们可以看出,单例模式包含的角色只有一个,就是单例类-Singleton。单例类拥有一个私有的构造函数,确保用户无法通过new关键字直接实例化它。除此之外,该模式中包含一个静态私有成员变量与静态公有的工厂方法,该工厂方法负责检验实例的存在性并实例化自己,然后存储在静态成员变量中,以确保只有一个实例被创建。

 

看完图,我们比较关心的是,代码要怎么实现呢?

从代码的实现角度来说,

1、将单例类的构造方法私有化,使外部无法通过new来实例化该类的对象

2、定义该类的静态私有对象

3、提供一个静态的公共方法用于创建或获取类的静态私有对象

 

接下来,我们就来看一下做成单例的几种方式。

首先,第一种,懒汉式,懒吗,等到用时才创建

public class Singleton {	private static Singleton singleton;	private Singleton() {	}	public static Singleton getInstance() {		if (singleton == null) {			singleton = new Singleton();		}		return singleton;	}}

  乍看之下,似乎没什么错误,但是如果放在多线程之下,我们看看会出现什么事吧~

/** * 测试类 *  * @author sun * */public class SingletonPattern {	public static void main(String[] args) {		MyThread myThread = new MyThread();		for (int i = 0; i < 20; i++) {			Thread thread = new Thread(myThread, String.valueOf(i));			thread.start();		}	}}class MyThread implements Runnable {	@Override	public void run() {		Singleton singleton = null;		Singleton instance = singleton.getInstance();		System.out.println(instance.toString());	}}

  技术分享

 从运行结果,我们可以看出,在多线程下,系统出现了不同的实例,违背了原来设计的初衷。

 

        造成这种情况的原因是因为,在多线程调用访问的时候,第一个调用getInstance方法的线程A,在判断完singleton是null的时候,线程A就进入了if块准备创造实例,但是同时另外一个线程B在线程A还未创造出实例之前,就又进行了singleton是否为null的判断,这时singleton仍然为null,但是线程B也会进入if块去创造实例,出现两个线程都进入if块创造实例,出现了不同值。

              为了避免这种情况,我们就要考虑多线程的情况了,我们最容易想到的方式使用synchronized关键字,直接将整个方法同步。

public class Singleton {    private static Singleton singleton;    private Singleton() {    }    public synchronized static Singleton getInstance() {        if (singleton == null) {            singleton = new Singleton();        }        return singleton;    }}

 

运行结果:

技术分享

可以看出,同步方法后,20个线程得到的是同一个实例。但是,每次调用getInstance()方法的时候都要同步,造成负担,效率减低。

那么,怎么解决呢?

首先检查是否已经创建实例,如果还没有创建,进行同步控制了,称为双重检查加锁

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

运行结果 

 

技术分享

 但是这样,还是有问题。

在jdk1.4及更早的版本中,会导致“双重检查加锁”的失败,因此“双重检查加锁”机制只能用在jdk1.5及以上的版本。

第三种:饿汉式,饿吗,在装载类的时候就创建对象实例。

public class Singleton {        private static Singleton singleton = new Singleton();        private Singleton(){}        public static Singleton getInstance(){        return singleton;    }    }

 第四、最好的解决方案,使用静态内部类

 

public class Singleton {
private Singleton() { } private static class SingletonInstance { private static Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonInstance.instance; }}

 

SingletonInstance类在装载并被初始化的时候,会初始化它的静态成员变量域,进而创建Singleton的实例,由于是静态变量,因此只会在虚拟机装载类的时候初始化一次,保证单例。






04 单例模式