单例模式
说明: 单例模式是一种常用的软件设计模式.在它的核心结构中只包含一个被称为单例的特殊类.通过单例模式可以保证系统中一个类只有一个实例.
1.懒汉式,线程不安全
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
2.双重检验锁,线程安全
public class Singleton {
/**
* 对保存实例的变量添加volatile的修饰
*/
private volatile static Singleton instance = null;
private Singleton (){}
public static Singleton getInstance() {
// 先检查实例是否存在,如果不存在才进入下面的同步块
if (instance == null) {
// 同步块,线程安全的创建实例
synchronized (Singleton.class) {
// 再次检查实例是否存在,如果不存在才真的创建实例
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
使用volatile关键字的原因: 禁止指令重排序优化
如果没有volatile关键字,JVM在执行instance = new Singleton()的时候,具体的执行内容:
- 给 instance 分配内存
- 调用 Singleton 的构造函数来初始化成员变量
- 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)
在JVM的即时编译器中存在指令重排序的优化.也就是说最终的执行顺序可能是1-3-2.在3执行完毕、2未执行之前,被线程二抢占了,这时 instance 已经是非null了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错.
3.饿汉式,线程安全
public class Singleton{
// 4: 定义一个静态变量来存储创建好的类实例,直接在这里创建类实例,只会创建一次
private static final Singleton instance = new Singleton();
//1: 私有化构造方法,好在内部控制创建实例的数目
private Singleton(){}
//2: 定义一个方法来为客户端提供类实例
//3: 这个方法需要定义成类方法,也就是要加static
public static Singleton getInstance(){
return instance;
}
}
对比
- 时间和空间:
- 懒汉式是典型的时间换空间
- 饿汉式是典型的空间换时间
- 线程安全:
- 不加同步的懒汉式是线程不安全的
- 饿汉式是线程安全的,因为虚拟机保证每个静态成员变量只会被当前的ClassLoader加载一次
4.静态内部类,线程安全
Lazy initialization holder class 模式,这个模式综合了java的类级内部类和多线程缺省同步锁的知识,很巧妙的同时实现了延迟加载和线程安全
public class Singleton {
/**
* 私有化构造方法
*/
private Singleton (){}
/**
* 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例没有绑定关系,
* 而且只有被调用到才会装载,从而实现了延迟加载
*/
private final static class SingletonHolder {
/**
* 静态初始化器,由JVM来保证线程安全
*/
private static final Singleton INSTANCE = new Singleton();
}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
使用JVM本身机制保证了线程安全问题:SingletonHolder是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖JDK版本.
5.枚举,线程安全
单元素的枚举类型是实现Singleton的最佳方法
- java的枚举类型实质上是功能齐全的类,可以有自己的属性和方法
- 枚举的思想是通过公有的静态final域为每个枚举常量导出实例的类
- 从某个角度讲,枚举是单例的泛型化,本质上是单元素的枚举
public enum Singleton {
/**
* 定义一个枚举的元素,它就代表了Singleton的一个实例
*/
INSTANCE;
/**
* 示意方法,单例可以有自己的操作
*/
public void singletonOperation(){
//功能处理
}
}
6.单例模式的本质
控制实例的数目
import java.util.HashMap;
import java.util.Map;
/**
* 扩展单例模式,控制实例数目为3个,
* 简单演示,实际使用要考虑线程安全和实例调度问题
*/
public class OneExtend {
/**
* 定义一个缺省的key值的前缀
*/
private final static String DEFAULT_PREKEY = "Cache";
/**
* 缓存实例的容器
*/
private static Map<String,OneExtend> map = new HashMap<String,OneExtend>();
/**
* 用来记录当前正在使用第几个实例,到了控制的最大数目,就返回从1开始
*/
private static int num = 1;
/**
* 定义控制实例的最大数目
*/
private final static int NUM_MAX = 3;
private OneExtend(){}
public static OneExtend getInstance(){
String key = DEFAULT_PREKEY+num;
OneExtend oneExtend = map.get(key);
if(oneExtend==null){
oneExtend = new OneExtend();
map.put(key, oneExtend);
}
//把当前实例的序号加1
num++;
if(num > NUM_MAX){
//如果实例的序号已经达到最大数目了,那就重复从1开始获取
num = 1;
}
return oneExtend;
}
}