04 单例模式

Posted tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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;
    }
    
} 

缺点:类加载即初始化实例,内存浪费

比如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、客户调用类的单个实例只允许一个公共访问节点


以上是关于04 单例模式的主要内容,如果未能解决你的问题,请参考以下文章

常用代码片段

常用代码片段

第04课:生活中的单例模式——你是我生命的唯一

第04课:生活中的单例模式——你是我生命的唯一

性能比较好的单例写法

04 单例模式