单例模式属于创建型模式
它只允许一个实例的存在,提供访问其唯一对象实例的方式,
避免频繁创建与销毁实例。
实现方式
单例模式实现方式有枚举、私有构造器两类。
私有构造器
这种实现方式最直观。
1 懒汉式
直到使用前才会创建实例。
package com.superv.resource.design.single;
public class Singleton {
/**
* Singleton对象唯一实例
*/
private static Singleton instance;
/**
* 私有构造器
*/
private Singleton() {
super();
// TODO Auto-generated constructor stub
}
/**
* 访问Singleton对象唯一实例方法
* @return
*/
public static Singleton getInstance () {
if (null == instance) { // 对象尚未被实例化
instance = new Singleton();
}
return instance;
}
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2); // true
}
}
不过这样在并发的情况下,可能会创建出不止一个实例。
在getInstance ()方法添加synchronized关键字,可以解决线程安全问题。
但是会导致效率低下。
2 饿汉式
类加载的时候就创建实例。
package com.superv.resource.design.single;
public class Singleton {
/**
* Singleton对象唯一实例
*/
private static Singleton instance = new Singleton();
/**
* 私有构造器
*/
private Singleton() {
super();
// TODO Auto-generated constructor stub
}
/**
* 访问Singleton对象唯一实例方法
* @return
*/
public static synchronized Singleton getInstance () {
return instance;
}
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2); // true
}
}
这样就不会存在线程安全的问题,
因为Java JVM类加载的特性,可以保证单实例。
可是有可能会产生很多无用的实例。
3 双重检查锁(推荐)
双重检查锁也是一种懒加载,
并且很好地解决了线程安全时效率低下的问题。
package com.superv.resource.design.single;
public class Singleton {
/**
* Singleton对象唯一实例
*/
private static volatile Singleton instance;
/**
* 私有构造器
*/
private Singleton() {
super();
// TODO Auto-generated constructor stub
}
/**
* 访问Singleton对象唯一实例方法
* @return
*/
public static synchronized Singleton getInstance () {
if (null == instance) { // 对象尚未被实例化
synchronized (Singleton.class) {
if (null == instance) { // 再次进行非空判断,以防加锁过程中对象被实例化
instance = new Singleton();
}
}
}
return instance;
}
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2); // true
}
}
// volatile:若变量在线程栈被修改,会强制同步到进程栈,
// 确保变化对其它的线程立即可见。
在程序执行过程中,出于性能考虑,在某些情况下会发生指令重排。
指令重排,是在字节码指令的层面,
在这里,对象实例化不是原子性的,有可能会出现下面的情况:
注:正常情况初始化顺序应为a、c、b。
volatile关键字可以避免指令重排,保证以a、c、b的顺序执行,
就不会出现上面的问题。
如果对volatile如何避免指令重排感兴趣,可以翻看文章下边的参考资料。
4 静态内部类(推荐)
静态内部类利用了类加载的特性确保单实例,同时又实现了延迟加载。
package com.superv.resource.design.single;
public class Singleton {
private static class SingletonHolder {
/**
* Singleton对象唯一实例
*/
private static volatile Singleton instance = new Singleton();
}
/**
* 私有构造器
*/
private Singleton() {
super();
// TODO Auto-generated constructor stub
}
/**
* 访问Singleton对象唯一实例方法
* @return
*/
public static synchronized Singleton getInstance () {
return SingletonHolder.instance;
}
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2); // true
}
}
Java JVM类加载时候不会加载静态内部类,使用的时候才会进行加载。
枚举
这种实现方式最优雅。
5 枚举(推荐)
代码简单到无法理解。
package com.superv.resource.design.single;
public enum Singleton {
INSTANCE;
public static void main(String[] args) {
Singleton instance1 = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance1 == instance2); // true
}
}
实现简单,并且通过反射等方法也无法破坏单例特性,是最为推荐的方式。
不当之处,请予指正。
参考资料:
vipwhr:单例模式五种实现;
jsbintask22:从未这么明白的设计模式(一):单例模式;
一指流砂~:深入理解设计模式(一):单例模式;
Wan_shibugong:volatile关键字的作用;
猴子007:volatile关键字的作用、原理;
大道方圆:Java内存模型与指令重排;
HelloWorld搬运工:volatile对指令重排的影响;