设计模式(1)单例模式

最近想重新回顾下设计模式,特此记录

一、概述

单例模式可以说是在开发中用得最多的一种模式了。以此来作为第一个回顾的模式。
单例模式简单来说分为懒汉式与饿汉式。

饿汉式

先看代码:

public class Singleton{
	private final static Singleton mSingleton =  new Singleton();
	private Singleton(){}
	public static Singleton getInstance(){
		return mSingleton;
	}
}

优点:这种写法比较简单,在类装载的时候就已经完成了实例化,属于线程安全;
缺点:不管需不需要,对象都存在,如果未使用,会造成内存浪费。

懒汉式

顾名思义,这种方式为Lazy Loading。只有在需要的时候才初始化实例。

懒汉式有多中写法,让我们来看看这种

public class Singleton {
    private static Singleton singleton;
    private Singleton() {}
    public static Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

咋一看,没啥毛病啊,singleton对象为null 才创建新的对象。确实,在没有高并发的情况下,这种写法没问题。但是当高并发存在时,假如一个线程A进入判断if(singleton == null) 为true时,准备创建新的对象但还未创建完成时(创建对象也是需要时间的),另一个线程B也进入到了if(singleton == null) 的判断,由于此时对象还未创建完成,所以AB两个线程会创建两个对象。

此种写法,线程不安全!

那么加上synchronized 关键字不就解决了吗?

public class Singleton {
    private static Singleton singleton;
    private Singleton() {}
    public static synchronized Singleton getInstance() {
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

线程安全完美解决。但是仔细一想,是不是有很大的弊端。
缺点:效率太低,每个线程想要获取实例的时候,都需要进行同步。

修改修改…
双重检查锁定

public class Singleton {
    private static Singleton singleton;
    private Singleton () {}
    public static Singleton getInstance() {
        //这次判空保证的多线程只有 singleton== null 时候才会加锁初始化,提高效率
        if (singleton== null) {
            synchronized (Singleton .class) {
            	//这次判断保证高并发下,代码块加锁后只执行一次实例化
                if (singleton== null) {
                    singleton= new Singleton ();
                }
            }
        }
        return singleton;
    }
}

看起来很完美了?保证了对象的唯一性,大幅降低了synchronized 带来的性能开销。一切只是看起来很完美。

指令重排

根据《The Java Language Specification, Java SE 7 Edition》(简称为java语言规范),
所有线程在执行java程序时必须要遵守 intra-thread semantics(译为 线程内语义是一个单线程程序的基本语义)。
intra-thread semantics 保证重排序不会改变单线程内的程序执行结果。
换句话来说,intra-thread semantics 允许那些在单线程内,不会改变单线程程序执行结果的重排序。

singleton= new Singleton () 在JVM中做了什么操作呢?

memory = allocate();   //1:分配对象的内存空间
ctorInstance(memory);  //2:初始化对象
singleton= memory;     //3:设置instance指向刚分配的内存地址

根据 intra-thread semantics 上述步骤中2 与 3 是有可能重排序的

TimeThread AThread B
t1A1:分配对象的内存空间
t2A3:设置instance指向刚分配的内存地址
t3B1:判断singleton == null
t4B2: singleton 不为null ,将访问singleton引用的对象
t5A2:初始化对象
t6A4:访问singleton对象

在此种情况下,线程B访问到的是一个没有初始化的对象
造成这种情况的唯一根源就是指令的重排
怎么解决这种情况呢。只需要做一点小修改,将声明对象的引用为volatile后即可,这样可以在多线程环境下禁止指令的重排

修改后方案

public class Singleton {
    private volatile static Singleton singleton;
    private Singleton () {}
    public static Singleton getInstance() {
        //这次判空保证的多线程只有 singleton== null 时候才会加锁初始化,提高效率
        if (singleton== null) {
            synchronized (Singleton .class) {
            	//这次判断保证高并发下,代码块加锁后只执行一次实例化
                if (singleton== null) {
                    singleton= new Singleton ();
                }
            }
        }
        return singleton;
    }
}

注意,这个解决方案需要JDK5或更高版本(因为从JDK5开始使用新的JSR-133内存模型规范,这个规范增强了volatile的语义)。

归根结底,单例模式需要考虑的就是线程安全与高性能,那么还有没有其他方式?

/**推荐**/
public class Singleton{
    private Singleton() {}
    static class SingletonHolder {
        private static final Singleton singleton= new Singleton();
    }
    public static Singleton getInstance() {
        return SingletonHolder.singleton;
    }
}

由于对象实例化是在内部类加载的时候构建的,因此该版是线程安全的(因为在方法中创建对象,才存在并发问题,静态内部类随着方法调用而被加载,只加载一次,不存在并发问题,所以是线程安全的)。

另外,在getInstance()方法中没有使用synchronized关键字,因此没有造成多余的性能损耗。当Singleton类加载的时候,其静态内部类SingletonHolder并没有被加载,因此singleton对象并没有构建。

而我们在调用Singleton.getInstance()方法时,内部类SingletonHolder被加载,此时单例对象才被构建。因此,这种写法节约空间,达到懒加载的目的,该版也是众多博客中的推荐版本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值