单例模式之懒汉单例(延迟初始化)多线程再解析

单例模式之懒汉单例(延迟初始化)多线程再解析


1、多线程下的懒汉单例:
	public class Lazysingleton {
		private static Lazysingleton m_instance = null;

		// 私有默认构造方法,外界无法直接实例化
		private Lazysingleton() {
		}

		// 静态工厂方法
		public static Lazysingleton getInstance() throws InterruptedException {
			// 延迟加载
			if (m_instance == null) {
				// 模拟创建对象的准备工作
				Thread.sleep(3000);
				m_instance = new Lazysingleton();// 初始化这个单例
			}
			return m_instance;
		}
	}
	public class MyThread extends Thread {

		@Override
		public void run() {
			try {
				System.out.println(Lazysingleton.getInstance().hashCode());
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	public class TestLazy1 {

		public static void main(String[] args) {
			MyThread t1=new MyThread();
			MyThread t2=new MyThread();
			MyThread t3=new MyThread();
			
			t1.start();
			t2.start();
			t3.start();
		}
	}
     单例模式之懒汉单例(延迟初始化)多线程再解析_第1张图片
打印结果说明创建出来三个对象,并不是单例,多线程下懒汉单例是非线程安全的。

多线程下单例模式非线程安全的解决方案:


1、声明synchronized关键字,实现同步方法
单例模式之懒汉单例(延迟初始化)多线程再解析_第2张图片
加入同步方法得到相同实例对象,此方法运行效率非常低,是同步运行,下一个线程想要取得对象,必须等上一个线程释放锁后,才能运行。
单例模式之懒汉单例(延迟初始化)多线程再解析_第3张图片

2、使用同步代码块
单例模式之懒汉单例(延迟初始化)多线程再解析_第4张图片
与使用synchronized同步方法一样是同步运行,效率非常低。得到相同实例对象。

3、部分代码上锁,进行单独同步,非线程安全
单例模式之懒汉单例(延迟初始化)多线程再解析_第5张图片
单例模式之懒汉单例(延迟初始化)多线程再解析_第6张图片
4、使用DCL双重检查锁定

单例模式之懒汉单例(延迟初始化)多线程再解析_第7张图片
使用DCL双重检查锁定成功解决懒汉模式的多线程问题,DCL也是大多数多线程结合单例模式使用的解决方案。
单例模式之懒汉单例(延迟初始化)多线程再解析_第8张图片
DCL是常见的延迟初始化技术,但有一个错误的用法。使用DCL需要一些技巧。

存在错误的根源:
单例模式之懒汉单例(延迟初始化)多线程再解析_第9张图片
a.多线程试图在同一时间创建对象,会通过加锁来保证只有一个线程创建对象。
b.在对象创建好后,执行getInstance()方法将不需要获取锁,直接返回创建好的对象。

问题:当代码读取到m_instance不为空,m_instance引用的对象有可能还没有完成初始化。就会出出现问题。

m_instance = new Lazysingleton();
可以分解为:
memory=allocate();1.分配对象的内存空间
ctorInstance(memory);2.初始化对象
instance=memory;3.设置instance指向刚分配的内存地址

在Java内存模型中为了优化代码会重排代码,会导致线程看到一个还没被初始化的对象。


线程安全的延迟初始化方案:

1、基于volatile的解决

声明volatile,初始化代码重排就会被禁止,此方案是通过禁止代码重排来实现线程安全的延迟加载。
创建对象的过程,实例化对象一般分为三个过程。
    1、分配内存空间。
    2 、初始化对象。
    3 、将内存空间地址赋值给对象的引用。
  但是由于重排序的缘故,步骤2、3可能会发生重排序,其过程如下
    1、分配内存空间
    2、将内存空间的地址赋值给对应的引用
    3、初始化对象
 如果不加volatile的话,可能线程1在初始化的时候重排序了,线程2看到singleton != null,已经返回singleton,其实线程1还没有完成初始化,仅仅只不过是分配了内存空间而已!

单例模式之懒汉单例(延迟初始化)多线程再解析_第10张图片

5、基于类初始化的解决方案(使用静态内置类实现单例)
单例模式之懒汉单例(延迟初始化)多线程再解析_第11张图片
除了使用DCL解决多线程单例模式的非线程安全的问题,使用静态内置类也可以实现同样的效果。
单例模式之懒汉单例(延迟初始化)多线程再解析_第12张图片









每天努力一点,每天都在进步。

你可能感兴趣的:(java设计模式探索之路)