设计模式——单例Singleton

若是你希望自己写的程序中的某个类只能有一个相对应的实例,那么这个时候就要用到单例模式了。单例模式是一种非常常见的设计模式,实现方法有好几种,下面将一一介绍:

1.懒汉式

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

将构造方法设为private的,这也令得我们无法在外部通过new来获得Singleton类的实例,我们只能通过该类提供的getInstance()方法来获得该类的一个实例。这种方法在单线程中是没有问题的,而且实现简单,但是在多线程环境下,设想一下多个线程同时访问该代码段,这将导致创建该类的多个实例,而这样就违背了它作为单例模式的初衷了,所以这种实现方法是线程不安全的。于是便有了下面这种改良版的懒汉式:

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

其实也就是在getInstance()方法前加了synchronized修饰,保证在同一时间只能有一个线程访问该方法,这样也就使得改良后的懒汉式是线程安全的。

2.双重检验锁
为什么会有双重检验锁呢,这是因为改良后的懒汉式虽然采用了加锁的方法解决了多实例的问题,但是其实我们只需要在第一次创建实例的时候进行同步操作而已,而懒汉式却是使得整个getInstance方法在任何时候都只能被一个线程所调用,这样做并不高效。于是便有了双重检验锁,也即double checked locking pattern,话不多说先上代码:

public class Singleton {
	private static Singleton instance;
	private Singleton(){}
	
	public static Singleton getInstance() {
		if(instance==null) {   //first checked
			synchronized (Singleton.class) {
				if(instance==null) {   //second checked
					instance = new Singleton();
				}
			}			
		}
		return instance;
	}
}

这里只是对new一个实例的这一部分进行了同步操作,进行两次判断是为了防止生成多个实例,因为可能有多个线程同时进入同步代码块外的第一个if判断语句。其实双重检验锁还有另外一种实现方式:

public class Singleton {
	private volatile static Singleton instance;
	private Singleton(){}
	
	public static Singleton getInstance() {
		if(instance==null) {   //first checked
			synchronized (Singleton.class) {
				if(instance==null) {   //second checked
					instance = new Singleton();
				}
			}			
		}
		return instance;
	}
}

是的,两种方式几乎是一模一样的,唯一不同的地方在于后者用volatile对instance进行了修饰。经过查阅资料了解到,"instance=new Singleton()"并不是一个原子操作,它将发生三个动作:(1)给instance分配内存(2)初始化Singleton的成员变量(3)将instance对象指向分配的内存空间。其中,(2)和(3)的顺序不是固定的,这样就有可能出现这样的情况:线程A访问该代码段,1和3的动作相继结束,2的动作还没开始,然而这个时候线程B抢占,由于3已经发生了,所以instance不等于null,线程B会返回尚未进行相关初始化的instance实例,这样程序就error了。所以,引入volatile修饰符,它能够禁止JVM对某代码块的指令重排序优化(没错,2和3发生的顺序不固定就是由于JVM的指令重排序优化),从而有效防止上面error的发生。

3.饿汉式

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

之所以叫饿汉式,是因为这种实现方法将使实例在类加载的时候便被创建(与懒汉式需要的时候才创建不同),从而也避免了多线程下的多实例问题。这样子虽然简单粗暴,但如果我们实在是需要在创建实例的时候传递些参数,那这种方法就不行了。

4.静态内部类

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

这是一种被称为Initialization Demand Holder (IoDH)的技术,在内部类中创建instance实例使得这个动作不会在类加载中就发生,而是当我们第一次调用getInstance()方法时才发生,并且利用了JVM的特性保证了线程安全性,即该变量仅能被赋值一次。

5.枚举
public enum Singleton{
INSTANCE;
}
这种实现方式简单到了极点,而且枚举类型本身就是线程安全的,看上去相当优美!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值