单例模式:一个类只能创建一个对象的设计模式
单例模式代码实现
懒汉模式
/*
* 要想让一个类只能构建一个对象,则不能让他随便进行new,因此构造方法是私有的
* 懒汉模式(如果单例初始值为null,则构建单例对象)
*/
public class Singleton {
//私有构造函数
private Singleton()
{
}
//单例对象
private static Singleton instance = null;
//静态工厂方法
public static Singleton getInstance()
{
if(instance == null)
{
instance = new Singleton();
}
return instance;
}
}
以上创建单例模式的方法是非线程安全的
假设Singleton类刚刚被初始化,instance对象还是空,这时候有两个线程A和B同时访问getInstance()方法。因为instance为空,所以两个线程都通过了条件判断,开始执行 new new Singleton()操作,致使instance被创建了两次。
饿汉模式
/*
* 饿汉模式:单例对象一开始就被new Singleton1()主动创建,不再进行判空操作
*/
public class Singleton1 {
//私有构造函数
private Singleton1()
{
}
//单例对象
private static Singleton1 instance = new Singleton1();
//静态工厂方法
public static Singleton1 getInstance()
{
return instance;
}
}
线程安全的单例模式
为了实现线程安全,在懒汉模式上我们进行改进,即防止new Singleton()被执行多次,这里可以使用Synchronized来进行同步。
public class Singleton2 {
//私有构造函数
private Singleton2()
{
}
//单例对象
private static Singleton2 instance = null;
//静态工厂方法
public static Singleton2 getInstance()
{
//双重检测
if(instance == null)
{
//使用同步锁,锁住整个类
synchronized (Singleton2.class) {
if(instance==null)
{
instance = new Singleton2();
}
}
}
return instance;
}
}
上面这种写法事实还是存在一定问题的
JVM编译器会对指令进行重排
instance=new Singleton()
其实在执行时分三步:
1、首先分配对象的内存空间
2、初始化对象
3、设置instance指向刚分配的内存地址
但是并不是一定按照1,2,3的顺序进行执行,CPU和JVM可能会对指令进行重排。
假设按照1,3,2的顺序进行执行。则线程A执行完1,3后,instance对象并未进行初始化,但是它已经不再指向null,此时如果线程B执行if判断,则结果是false,直接return instance,这里反悔了一个没有初始化完成的instance对象。
为了避免这种情况的发送,即避免指令重排,我们可以在instance对象前加上修饰符volatile
volatile阻止了变量访问前后的指令重拍,保证了指令执行顺序。
volatile在多处理器开发中保证了共享变量的可见性。
由于现在的操作系统,都有缓存机制。当一个线程执行时,它会维护一个私有的堆栈,与公共堆栈中的数据并不是时刻保持一致的。正是由于缓冲区和内存区数据的不一致性,导致程序执行结果出现差别。volatile通过内存屏障,强制从公共堆栈中取得变量的值。
内存屏障指令在多核处理器下会引发两件事:
将当前处理器缓存行的数据写会到系统内存
这个写会内存的操作会使其他CPU里缓存了该内存地址的数据无效。
public class Singleton3 {
//私有构造函数
private Singleton2()
{
}
//单例对象
private volatile static Singleton2 instance = null;
//静态工厂方法
public static Singleton2 getInstance()
{
//双重检测
if(instance == null)
{
//使用同步锁,锁住整个类
synchronized (Singleton2.class) {
if(instance==null)
{
instance = new Singleton2();
}
}
}
return instance;
}
}
静态内部类实现
public class Singleton3 {
private Singleton3(){}
private static class LazyHolder{
private static final Singleton3 INSTANCE = new Singleton3();
}
public static Singleton3 getInstance()
{
return LazyHolder.INSTANCE;
}
}
从外部是无法访问静态内部类LazyHolder的 ,只有调用Singleton.getInstance()方法,才能得到单例对象INSTANCE。这是种懒加载的方式,只有执行Singleton.getInstance(),INSTANCE对象才被初始化。
以上的方式都有一个问题,就是无法防止利用反射来重新构建对象
先来看一下如何利用反射打破单例的约束
public class BreakSingletonByReflect {
public static void main(String[] args) throws Exception{
//先获取单例类的构造器
Constructor<Singleton3> con = Singleton3.class.getDeclaredConstructor();
//设置改构造器为可访问
con.setAccessible(true);
//构造两个不同的对象
Singleton3 singleton1 = con.newInstance();
Singleton3 singleton2 = con.newInstance();
System.out.println("两个对象是否相等"+singleton1.equals(singleton2));
}
}
输出结果是:两个对象是否相等false
如何防止反射来重建单例对象?使用枚举的方式
用枚举实现
public enum SingletonEnum{
INSTANCE;
private SingletonEnum()
{
....
....
....
}
}
参考http://mp.weixin.qq.com/s/2UYXNzgTCEZdEfuGIbcczA