不少时候为了节约系统资源,须要确保系统中某个类只有一个惟一的实例,当这个惟一实例建立了以后,没法再建立一个同类型的其余对象,全部的操做只能基于这一个惟一实例。这是单例模式的动机所在。java
好比Windows的任务管理器,能够按Ctrl+Shift+Esc
启动,并且启动一个,不能启动多个。编程
单例模式(Singleton Pattern):确保某一个类只有一个实例,并且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。安全
单例模式是一种对象建立型模式。微信
单例模式只有一个角色:多线程
Singleton
(单例角色):在单例类的内部只生成一个实例,同时它提供一个相似名叫getInstance
的静态方法获取实例,同时为了防止外部生成新的实例化对象,构造方法可见性为private
,在单例类内部定义了一个Singleton
的静态对象,做为供外部访问的惟一实例new
等方式建立对象getInstance()
的公有静态方法来获取实例单例角色一般实现以下:并发
class Singleton { //饿汉式实现 private static Singleton instance = new Singleton(); private Singleton(){} public static Singleton getInstance() { return instance; } }
客户端直接经过该类获取实例便可:负载均衡
Singleton singleton = Singleton.getInstance();
某个软件须要使用一个全局惟一的负载均衡器,使用单例模式对其进行设计。
代码以下:编程语言
public class LoadBalancer { private static LoadBalancer instance = null; private LoadBalancer(){} public static LoadBalancer getInstance() { return instance == null ? instance = new LoadBalancer() : instance; } public static void main(String[] args) { LoadBalancer balancer1 = LoadBalancer.getInstance(); LoadBalancer balancer2 = LoadBalancer.getInstance(); System.out.println(balancer1 == balancer2); } }
这是最简单的单例类的设计,获取实例时仅仅判断是否为null
,没有考虑到线程问题。也就是说,多个线程同时获取实例时,仍是有可能会产生多个实例,通常来讲,常见的解决方式以下:函数
饿汉式单例就是在普通的单例类基础上,在定义静态变量时就直接实例化,所以在类加载的时候就已经建立了单例对象,并且在获取实例时不须要进行判空操做直接返回实例便可:性能
public class LoadBalancer { private static LoadBalancer instance = new LoadBalancer(); private LoadBalancer(){} public static LoadBalancer getInstance() { return instance; } }
当类被加载时,静态变量instance
被初始化,类的私有构造方法将被调用,单例类的惟一实例将被建立。
懒汉式单例在类加载时不进行初始化,在须要的时候再初始化,加载实例,同时为了不多个线程同时调用getInstance()
,能够加上synchronized
:
public class LoadBalancer { private static LoadBalancer instance = null; private LoadBalancer(){} synchronized public static LoadBalancer getInstance() { return instance == null ? instance = new LoadBalancer() : instance; } }
这种技术又叫延迟加载技术,尽管解决了多个线程同时访问的问题,可是每次调用时都须要进行线程锁定判断,这样会下降效率。
事实上,单例的核心在于instance = new LoadBalancer()
,所以只须要锁定这行代码,优化以下:
public static LoadBalancer getInstance() { if(instance == null) { synchronized (LoadBalancer.class) { instance = new LoadBalancer(); } } return instance; }
可是实际状况中仍是有可能出现多个实例,由于若是A和B两个线程同时调用getInstance()
,都经过了if(instance == null)
的判断,假设线程A先得到锁,建立实例后,A释放锁,接着B获取锁,再次建立了一个实例,这样仍是致使产生多个单例对象。
所以,一般采用一种叫“双重检查锁定”的方式来确保不会产生多个实例,一个线程获取锁后再进行一次判空操做:
private volatile static LoadBalancer instance = null; public static LoadBalancer getInstance() { if(instance == null) { synchronized (LoadBalancer.class) { if(instance == null) { instance = new LoadBalancer(); } } } return instance; }
须要注意的是要使用volatile
修饰变量,volatile
能够保证可见性以及有序性。
为了克服饿汉式不能延迟加载以及懒汉式的线程安全控制繁琐问题,可使用一种叫Initialization on Demand Holder(IoDH)
的技术。实现IoDH时,需在单例类增长一个静态内部类,在该内部类中建立单例对象,再将该单例对象经过getInstance()
方法返回给外部使用,代码以下:
public class LoadBalancer { private LoadBalancer(){} private static class HolderClass { private static final LoadBalancer instance = new LoadBalancer(); } public static LoadBalancer getInstance() { return HolderClass.instance; } }
因为单例对象没有做为LoadBalancer
的成员变量直接实例化,所以类加载时不会实例化instance
。首次调用getInstance()
时,会初始化instance
,由JVM保证线程安全性,确保只能被初始化一次。另外相比起懒汉式单例,getInstance()
没有线程锁定,所以性能不会有任何影响。
经过IoDH既能够实现延迟加载,又能够保证线程安全,不影响系统性能,可是缺点是与编程语言自己的特性相关,不少面向对象语言不支持IoDH。另外,还可能引起NoClassDefFoundError
(当初始化失败时),例子能够戳这里。
其中,不管是饿汉式,仍是懒汉式,仍是IoDH,都有或多或少的问题,而且还能够经过反射以及序列化/反序列化方式去“强制”生成多个单例,有没有更优雅的解决方案呢?
有!答案就是枚举。
代码以下:
public class Test { public static void main(String[] args) { LoadBalancer balancer1 = LoadBalancer.INSTANCE; LoadBalancer balancer2 = LoadBalancer.INSTANCE; System.out.println(balancer1 == balancer2); } } enum LoadBalancer{ INSTANCE; }
使用枚举实现单例优势以下:
若是以为文章好看,欢迎点赞。
同时欢迎关注微信公众号:氷泠之路。