单例模式
一、饿汉式单例
饿汉式单例是在类加载的时候就立即初始化,并且创建单例对象。绝对线程安全,在线
程还没出现以前就是实例化了,不可能存在访问安全问题。
优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。
缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存,有可能占着茅
坑不拉屎。
Spring中IOC容器ApplicationContext本身就是典型的饿汉式单例。接下来看一段代
码:
//饿汉式单例
// 它是在类加载的时候就立即初始化,并且创建单例对象
//优点:没有加任何的锁、执行效率比较高,
//在用户体验上来说,比懒汉式更好
//缺点:类加载的时候就初始化,不管你用还是不用,我都占着空间
//浪费了内存,有可能占着茅坑不拉屎
//绝对线程安全,在线程还没出现以前就是实例化了,不可能存在访问安全问题
public class HungrySingleton {
//先静态、后动态
//先属性、后方法
//先上后下
private static final HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
还有另外一种写法,利用静态代码块的机制:
//饿汉式静态块单例
public class HungryStaticSingleton {
private static final HungryStaticSingleton hungrySingleton;
static {
hungrySingleton = new HungryStaticSingleton();
}
private HungryStaticSingleton(){}
public static HungryStaticSingleton getInstance(){
return hungrySingleton;
}
}
二、懒汉式单例
懒汉式单例的特点是:被外部类调用的时候内部类才会加载,下面看懒汉式单例的简单
实现LazySimpleSingleton:
//懒汉式单例
//在外部需要使用的时候才进行实例化 线程不安全
public class LazySimpleSingleton {
private LazySimpleSingleton(){}
//静态块,公共内存区域
private static LazySimpleSingleton lazy = null;
public static LazySimpleSingleton getInstance(){
if(lazy == null){
lazy = new LazySimpleSingleton();
}
return lazy;
}
}
实现线程安全的单例:
//懒汉式单例
//在外部需要使用的时候才进行实例化
public class LazySimpleSingleton {
private LazySimpleSingleton(){}
//静态块,公共内存区域
private static LazySimpleSingleton lazy = null;
//加锁,保证线程安全,但是锁住的粒度太大,导致性能低但是,用
//synchronized加锁,在线程数量比较多情况下,如果CPU 分配压力上升,会导致大批
//量线程出现阻塞,从而导致程序运行性能大幅下降。
public synchronized static LazySimpleSingleton getInstance(){
if(lazy == null){
lazy = new LazySimpleSingleton();
}
return lazy;
}
}
双重校验方式实现单例:
public class LazyDoubleCheckSingleton {
//volatile是为了让该变量有可见性以及禁止重排序
private volatile static LazyDoubleCheckSingleton lazy = null;
private LazyDoubleCheckSingleton(){}
public static LazyDoubleCheckSingleton getInstance(){
if(lazy == null){
synchronized (LazyDoubleCheckSingleton.class){
if(lazy == null){
lazy = new LazyDoubleCheckSingleton();//第7行
//1.分配内存给这个对象
//2.初始化对象
//3.设置lazy指向刚分配的内存地址
//4.初次访问对象
}
}
}
return lazy;
}
}
双重校验锁为什么要加volatile关键字修饰?
lazy = new LazyDoubleCheckSingleton();//第7行这个创建了一个对象,这一行代码可以分解为
如下的3行伪代码。
memory=allocate();//1.分配对象的内存空间
ctorInstance(memory);//2初始化对象
instance=memory;//3.设置instance指向刚分配的内存地址
上面3行伪代码中的2和3之间,可能会被重排序,2和3之间重排序之后的执行时序如下:
1 3 2
由于可能存在指令重排序,即线程A执行了第3步,没执行第2步,即对象并未初始化完成,导致线程B在判断对象是否为空时,获取到了一个未完全初始化完成的对象,所以要通过volatile来禁止指令重排序;
//这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题
//完美地屏蔽了这两个缺点
//史上最牛B的单例模式的实现方式
public class LazyInnerClassSingleton {
//默认使用LazyInnerClassGeneral的时候,会先初始化内部类
//如果没使用的话,内部类是不加载的
private LazyInnerClassSingleton(){
if(LazyHolder.LAZY != null){
throw new RuntimeException("不允许创建多个实例");
}
}
//每一个关键字都不是多余的
//static 是为了使单例的空间共享
//保证这个方法不会被重写,重载
public static final LazyInnerClassSingleton getInstance(){
//在返回结果以前,一定会先加载内部类
return LazyHolder.LAZY;
}
//默认不加载
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
三:容器式单例
//Spring中的做法,就是用这种注册式单例
public class ContainerSingleton {
private ContainerSingleton(){}
private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();
public static Object getInstance(String className){
synchronized (ioc) {
if (!ioc.containsKey(className)) {
Object obj = null;
try {
obj = Class.forName(className).newInstance();
ioc.put(className, obj);
} catch (Exception e) {
e.printStackTrace();
}
return obj;
} else {
return ioc.get(className);
}
}
}
}
容器式写法适用于创建实例非常多的情况,便于管理。但是,是非线程安全的。到此,
注册式单例介绍完毕。
四、枚举单例
//常量中去使用,常量不就是用来大家都能够共用吗?
//通常在通用API中使用
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance(){
return INSTANCE;
}
}