[java设计模式] 我眼中的单例模式(二)

上一篇[java设计模式] 我眼中的单例模式(一) 我们介绍了一些单例(单件)模式的基本概念,本文将对其中介绍的几种实现方式进行介绍:


1. 饿汉模式

类加载的时候就创建实例

优点:类加载的时候就创建一次实例,避免了多线程同步问题
缺点:即使单例没被用到也会创建,浪费内存

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

因构造函数私有化,所以外部无法进行正常实例化,只能调用 getInstance()方法获得实例

饿汉模式的变种:


public class Singleton{
	private static Singleton instance = null;
	static {
		instance = new Singleton();
	}
	private Singleton(){}
	public static Singleton getInstance(){
		return this.instance;
	}
}

2. 懒汉模式(非线程安全)

优点:需要的时候才创建
缺点:没有考虑线程安全问题,多个线程并发调用getIntance,可能会创建多个实例。

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

懒汉模式 (线程安全)

缺点:性能问题,添加了synchronzied函数比一般的方法慢得多,若多次调用getInstance,则性能损耗特别大。

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

于是,考虑到以上的性能问题,又有一种双重校验锁的实现方式:

双重校验锁

大部分情况下,大部分都不会执行到,提高了程序的性能。

有一种情况,两个线程threadA,threadB,如果threadA执行到了第一个if条件判断,instance = null; threadB也执行到了if条件判断,instance = null,所以A和B会依次执行同步代码块里面的代码。为了避免创建两个实例,因此又在同步代码块中添加了if条件进行了二重校验。

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

隐患:

  1. 此处涉及Java的指令重排优化。指令重排优化是指在不改变原语义的情况下,通过调整指令的执行顺序让程序运行的更快。
  2. JVM没有固定编译器优化的相关内容,也即JVM可以自由地进行指令重排序优化
  3. 此问题的关键在于由于指令重排序优化的存在,导致初始化Singleton和将对象地址赋给instance字段的顺序是不确定的。
  4. 在某个线程创建单例对象时,在构造函数被调用前,就为该对象分配了内存空间并将对象的字段设为默认值。此时就可以将分配的内存地址赋值给instance字段了,然后该对象可能还没初始化。若紧接着另一个线程来调用getInstance,获取到的就是状态不正确的对象,程序出错。

JDK1.5的修正:以上是双重校验锁失效的原因,不过在jdk1.5及以后的版本添加了volatile关键字。

  1. volatile的语义是禁止指令重排序优化,也就保证了instance变量被赋值的时候对象已经是初始化过的了,从而避免了上述问题。

  2. Java中的volatile变量是什么?

    1)关键字的作用有两个:

    • 多线程主要围绕可见性和原子性两个特性展开,使用volatile关键字修饰的变量,保证了其在多线程之间的可见性,即每次读取到的volatile变量,一定是最新的数据。
    • 代码底层执行的顺序是Java代码 -> 字节码 -> 根据字节码执行对应的C/C++ 代码 -> C/C++代码被编译成汇编语言 -> 和硬件电路交互。实际中,为了获取更好的性能,JVM可能会对执行进行重排序,多线程下可能会出现一些意想不到的问题。使用volatile则会禁止语义重排序,也一定程度上降低了代码执行效率。实践角度而言,volatile的一种重要作用就是和CAS结合,保证了原子性。
      2)volatile是一个特殊的修饰符,只有成员变量可以使用它。在Java并发程序缺少同步类的情况下,多线程对成员变量的操作读其他线程是透明的。volatile变量可以保证下一个读取操作会在前一个写操作之后发生。
public class Singleton{
	private static volatile Singleton instance = null;
	private Singleton(){}
	public static Singleton getInstance(){
		if(instance == null){
			sychronized(Singleton.class){
				if(instance == null){
					instance = new Singleton();
				}
			}
		}
	return instance;
	}
}


3. 静态内部类

public class StaticSingleton{
	private StaticSingleton(){}
	private static class SingletonHolder{
		private static StaticSingleton INSTANCE = new StaticSingleton();
	}
	
	public static StaticSingleton getInstance(){
		return SingletonHolder.INSTANCE;
	}
}


既能避免多线程,又能防止反序列化重新创建新的对象。

参考:

  • 来自CSDN
  • 《Head First 设计模式》 单件模式 Page@169-189
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值