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

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

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();
		}
	}

    
打印结果说明创建出来三个对象,并不是单例,多线程下懒汉单例是非线程安全的。

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

2.1、声明synchronized关键字,实现同步方法

加入同步方法得到相同实例对象,此方法运行效率非常低,是同步运行,下一个线程想要取得对象,必须等上一个线程释放锁后,才能运行。


2.2、使用同步代码块

与使用synchronized同步方法一样是同步运行,效率非常低。得到相同实例对象。

2.3、部分代码上锁,进行单独同步,非线程安全


2.4、使用DCL双重检查锁定


使用DCL双重检查锁定成功解决懒汉模式的多线程问题,DCL也是大多数多线程结合单例模式使用的解决方案。

DCL是常见的延迟初始化技术,但有一个错误的用法。使用DCL需要一些技巧。

存在错误的根源:

  • a.多线程试图在同一时间创建对象,会通过加锁来保证只有一个线程创建对象。

  • b.在对象创建好后,执行getInstance()方法将不需要获取锁,直接返回创建好的对象。
     

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

 

m_instance = new Lazysingleton();

可以分解为:

  • memory=allocate();1.分配对象的内存空间

  • ctorInstance(memory);2.初始化对象

  • instance=memory;3.设置instance指向刚分配的内存地址


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


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

3.1、基于volatile的解决

声明volatile,初始化代码重排就会被禁止,此方案是通过禁止代码重排来实现线程安全的延迟加载。
创建对象的过程,实例化对象一般分为三个过程。

  • 1、分配内存空间。

  • 2 、初始化对象。

  • 3 、将内存空间地址赋值给对象的引用。

但是由于重排序的缘故,步骤2、3可能会发生重排序,其过程如下

  • 1、分配内存空间

  • 2、将内存空间的地址赋值给对应的引用

  • 3、初始化对象

 如果不加volatile的话,可能线程1在初始化的时候重排序了,线程2看到singleton != null,已经返回singleton,其实线程1还没有完成初始化,仅仅只不过是分配了内存空间而已!

 

3.2、基于类初始化的解决方案(使用静态内置类实现单例)

这里有几个需要注意的点:

1.从外部无法访问静态内部类MyObiectHandler,只有当调用getInstance方法的时候,才能得到单例对象INSTANCE。

2.INSTANCE对象初始化的时机并不是在单例类Singleton被加载的时候,而是在调用getInstance方法,使得静态内部类MyObiectHandler被加载的时候。因此这种实现方式是利用classloader的加载机制来实现懒加载,并保证构建单例的线程安全。

 

除了使用DCL解决多线程单例模式的非线程安全的问题,使用静态内置类也可以实现同样的效果。










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

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

powerfuler

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值