单例模式确保某一个类只有一个实例,自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。
单例模式的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; } }
缺点:类加载即初始化实例,内存浪费。
比如Runtime类就采用了这种方式。
public class Runtime { private static Runtime currentRuntime = new Runtime(); /** * Returns the runtime object associated with the current Java application. * Most of the methods of class <code>Runtime</code> are instance * methods and must be invoked with respect to the current runtime object. * * @return the <code>Runtime</code> object associated with the current * Java application. */ public static Runtime getRuntime() { return currentRuntime; } }
第四、最好的解决方案,使用静态内部类
public class Singleton {
private Singleton() { } private static class SingletonInstance { private static Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonInstance.instance; } }
SingletonInstance类在装载并被初始化的时候,会初始化它的静态成员变量域,进而创建Singleton的实例,由于是静态变量,因此只会在虚拟机装载类的时候初始化一次,保证单例。
适用场景:
1、系统只需要一个实例对象,因资源大而只允许创建一个。
2、客户调用类的单个实例只允许一个公共访问节点