单例模式

1.单例模式确保某各类只有一个实例,必须自己创建自己的唯一实例。一般通过将构造方法声明为private避免类在外部被实例化,在同一虚拟机范围内,Singleton的唯一实例只能通过getInstance()访问。

2.实现方式:

方案一:懒汉式单例类,在第一次调用的时候实例化自己

public class Singleton {
	private Singleton() {}
	private static Singleton singleton = null;
	//静态工厂方法
	public static Singleton getInstance() {
		if (singleton == null) {           //1
			singleton = new Singleton(); //2
		}
		return singleton;                  //3
	}
}

分析:上面的懒汉式没有考虑线程安全问题,在并发环境下很可能会出现多个Singleton实例。

上面的程序只适用于单线程程序,当有2个线程调用getInstance方法时:
  1. 线程1调用getInstance()方法,并判断singleton在//1处为null;
  2. 线程1进入if代码块,但还未执行//2处;
  3. 线程2调用getInstance()方法,并判断singleton在//1处为null;
  4. 线程2进入if代码块,并创建一个新的Singleton对象并在//2处将变量singleton分配给这个新对象;
  5. 线程2在//3处返回singleton对象;
  6. 线程1在它停止的地方启动,并执行//2代码,这导致创建另一个Singleton对象;
  7. 线程1在//3处返回singleton对象;
结果就是getInstance()方法创建了两个Singleton对象。

方案二.在getInstance()方法上加同步

public static synchronized Singleton getInstance() {
	if (singleton == null) {            //1
		singleton = new Singleton();  //2
	}
	return singleton;                   //3
}
存在的问题:
  由于只有第一次调用执行了//2处的代码,而只有此行代码需要同步;
  因此就无需对后续调用使用同步,所有其他调用判断singleton是非null,然后将其返回

  由于方法是synchrinized的,需要为方法的每一步调用付出同步的代价。

方案三.双重检查锁定:

public static Singleton getInstance() {
	if (singleton == null) {
		synchronized (Singleton.class) {         //1
			if (singleton == null) {           //2
				singleton = new Singleton(); //3
			} 
		}
	}
	return singleton;
}

当singleton为null时,两个线程可以并发地进入if语句内部,然后一个线程进入synchronized块来初始化singleton,而另一个线程则被阻断。当第一个线程退出synchronized块时,等待着的线程进入并创建另一个Singleton对象,但是当第二个线程进入synchronized块时,它必须检查singleton是否为null。

存在的问题:(JMM的指令重排序)
  在代码//3处,创建一个Singleton对象并初始化变量singleton来引用该对象
  这行代码出现的问题是:在Singleton构造函数体执行之前,变量singleton可能成为非null
  
  1.线程1进入getInstance()方法
  2.由于singleton为null,线程1在//1处进入synchronized块
  3.线程1进入到//3处,但是在构造函数执行之前,实例singleton非null
  4.线程1被线程2预占
  5.线程2检查singleton是否为null,因为singleton不为null,线程2将singleton引用返回
  给一个构造完整但部分初始化了的Singleton对象
  6.线程2被线程1预占
  7.线程1通过运行Singleton对象的构造函数并将引用返回给它,完成对象的初始化
  上述过程发生了线程2返回一个尚未执行构造函数的对象。
  代码:singleton = new Singleton();
    1.mem = allocate();             //Allocate memory for Singleton object.
    2.instance = mem;               //Note that instance is now non-null, but has not been initialized.
    3.ctorSingleton(instance);      //Invoke constructor for Singleton passing instance.

  方案四.静态内部类(建议使用)

public class Singleton {
	private Singleton() {}
	private static class SingletonHolder {
		private final static Singleton singleton = new Singleton();
	}
	public static Singleton getInstance() {
		return SingletonHolder.singleton;
	}
}
定义一个私有的内部类,在第一次用这个嵌套类时,会创建一个实例。
而类型为SingletonHolder的类,只有在Singleton.getInstance()中调用,
由于私有的属性,他人无法使用SingleHolder,不调用Singleton.getInstance()就不会创建实例。

达到了lazy loading的效果,即按需创建实例。

方案五.饿汉式单例(建议使用)

public class Singleton {
	private Singleton() {}
	private static Singleton singleton = new Singleton();
	//静态工厂方法
	public static Singleton getInstance() {
		return singleton;
	}
}
饿汉式和懒汉式区别
  1.饿汉式是类一旦加载,就把单例初始化完成,保证getInstance的时候,单例已经存在
  懒汉式只有当调用getInstance方法时,才初始化这个类;
  2.线程安全
  饿汉式天生就是线程安全的,可直接用于多线程而不会出现问题
  懒汉式非线程安全的
  3.资源加载和性能
  饿汉式在类创建的同时就实例化一个静态对象,不管之后会不会使用这个单列,都会占用一定的内存,
  但是相应的在第一次调用的时候速度更快,因为其资源已经初始化完成;
  懒汉式会延迟加载,在第一次使用该单例的时候才会实例化该对象;
  方案二在方法调用上加了同步,虽然线程安全,但是每次都要同步,影响性能
  方案三在getInstance中做了两次null检查,但是会由于JMM的指令重排序,可能会出现线程不安全情况;
  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值