定义:
单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。
从定义来看,有几个要点:
这个类只有一个实例
这个类要自行实例化,也就是说它不能从外部实例化该类,不能在外部用new 方式来创建一个实例。
- 这个类要向整个系统提供这个类。也就是说它需要有一个向外部暴露类的唯一实例的方法,让外部可以得到类的实例。否则就无法使用了。
uml图例
常用代码:
public class Singleton{
static private Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if(instance==null){
instance = new Singleton();
}
return instance;
}
}
要点:
- 构造方法私有化。private Singleton(),将构造方法私有化,那么就无法在外部创建实例。
- 一个静态的向外部暴露唯一实例的方法:getInstance();第一次调用时,创建静态实例instance,之后再调用则直接返回已经创建好的实例。
-
饿汉式单例与懒汉式单例
现在用上面的代码创建一个重量级的单例对象:资源调度器(重量级表示这个对象很沉重,涉及许多的初始化操作或者要占用很多资源)
public class ResourceBalance{
...(大量初始化操作)
private static ResourceBalance instance = null;
private ResourceBalance(){}
public static ResourceBalance getBalance(){
if(instance ==null){
instance = new ResourceBalance();
}
return instance;
}
}
在实际调用的时候,却发现还是出现了ResourceBalance的多个实例,并没有完全实现单例(通常是在多线程环境中)。经分析发现:这是因为第一次调用getBalance()方法时,由于这个类需要大量的初始化操作,所以创建类实例需要一定的时间。在这个过程中,第二次调用getBalance()方法,这时instance仍为null,所以会再一次创建实例,导致多实例的产生。
为解决这个问题,一般有两种解决方法:饿汉式单例和懒汉式单例
- 饿汉式单例
public class ResourceBalance{
...(大量初始化操作)
private Static ResourceBalance instance = new Resource();
private ResourceBalance(){}
public static ResourceBalance getBalance(){
return instance;
}
}
当类被加载时,静态变量instance会被初始化,此时私有构造函数会被调用,从而创建实例。用这种方式可以避免创建多个实例。不过缺点在于就算不需要使用该类,也会创建类实例,一定程度上浪费资源。
- 懒汉式单例
双重检查锁定
public class ResourceBalance{
...(大量初始化操作)
private volatile static ResourceBalance instance = null;
private ResourceBalance(){}
public static ResourceBalance getBalance(){
if(instance==nul){
synchronized (ResourceBalance .class) {
if(instance==nul){
instance = new ResourceBalance();
}
}
}
return instance;
}
}
注意:
需要在静态成员变量instance之前增加修饰符volatile,被volatile修饰的成员变量可以确保多个线程都能够正确处理,且该代码只能在JDK 1.5及以上版本中才能正确执行。由于volatile关键字会屏蔽Java虚拟机所做的一些代码优化,可能会导致系统运行效率降低,因此即使使用双重检查锁定来实现单例模式也不是一种完美的实现方式。
两者优劣
饿汉式在类初始化时就实例化唯一对象,而且避免了重复创建对象的问题,无需考虑多线程问题。所以从调用时间和反应速度来说由于懒汉式单例。但同时因为这个原因,即使不需要使用该类,也会创建唯一实例,所以从资源利用率上来说,懒汉式单例更好一些。
懒汉式使用了双重锁定,在代码优化方面会使一些虚拟机设置失效。导致性能有所下降。
完美方案
饿汉式和懒汉式单例都有自己的优点和缺点,那么有没有一种方法既可以避免多线程环境下的多实例创建问题,又能够延迟加载,不影响系统系能,保证资源利用效率呢。答案是有的!
我们在单例类中增加一个静态(static)内部类,在该内部类中创建单例对象,再将该单例对象通过getInstance()方法返回给外部使用
public class ResourceBalance{
...(大量初始化操作)
private ResourceBalance(){}
private static class HoldClass(){
private static final instance = new ResourceBalance();
}
public static ResourceBalance getBalance(){
return HoldClass.instance;
}
}
由于没有将唯一实例instance作为类的成员变量,所以类加载时也就不会创建实例。在第一次调用getBalance()时将加载内部类,初始化内部类过程中创建ResourceBalance类的实例instance,唯一性由java虚拟机保证。由于没有添加synchronized 限定,也不会影响系统性能。不失为一种攻守兼备的实现方式。
这种方式局限性在于Java可以这样实现,有些语言中不支持。
实际项目中,看到一个比较有意思的单例实现方式。
public class LockManager {
private LockManager() {}
public static LockManager getLockManager() {
return LockManager.Singleton.INSTANCE;
}
private interface Singleton {
LockManager INSTANCE = new LockManager();
}
}
接口的任何域都默认static的,用接口来代替静态内部类,也是一个很好的想法。
适用场景:
- 系统只需要一个对象实例,例如序列码生成器,资源调度器等。或者创建对象所需要的资源很多,只允许创建一个实例。
过往实例:
- Window的Task Manager(任务管理器)
- 网站计数器
- 数据库连接池(单例的变种:多例)