五、单例模式Singleton(创建型)

保证一个类仅有一个实例,并提供一个访问它的全局访问点。让类自身负责保存它的唯一实例,这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。例如在全局只有一个工厂,这个工厂可以生产你注册进去的任意组件,那么这个工厂就适合使用单例模式来创建。


1.常用单例方式

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

这是最常用的单例模式,这里设置了构造方法为private,使得其他类无法实例化它,然后在内部提供静态的实例,并提供外部可以访问的单一访问点getInstance方法,保证只有在全局没有实例时才会创建新的实例。

2.可以进行注册的单例

public class Singleton2 {
	protected Singleton2(){}
	
	private static Singleton2 instance = null;
	private static Map<String, Singleton2>  repository= new HashMap<>();
	
	private static Singleton2 lookup(String name){
		return repository.get(name);
	}
	public static void register(String name, Singleton2 instance){
		repository.put(name, instance);
	}
	public static Singleton2 getInstance(){
		if(instance == null){
			String name = System.getProperty("SINGLETON");
			instance = lookup(name);
		}
		return instance;
	}
}
public class ConcreteSingleton2 extends Singleton2{
	public ConcreteSingleton2(){
		register("ConcreteSingleton2", this);
	}
	@Test
	public void test(){
		//初始化时必须这么做
		new ConcreteSingleton2();
		System.setProperty("SINGLETON", "ConcreteSingleton2");
		//实际使用时
		Singleton2 s = Singleton2.getInstance();
		System.out.println(s);;
	}
}

这是设计模式书中的注册Singleton,Singleton父类提供注册、检索、获取单件的方法,子类可以使用父类提供的方法向父类提供的单件注册表注册自己,然后整个系统就可以通过实现新的子类、向系统注册这个子类、设置环境变量,最后通过总的获取方法来获得你在环境变量中指定的单件实例。

3.使用synchronized同步的单例(线程安全)

以上所讲的两种方式是设计模式书中的单例方法,但是在Java中,多线程程序非常常见,所以需要考虑多线程下该如何做,最简单的方式是直接使用Java提供的synchronized关键字来改造方法1中的单例方法。

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

4.使用static域的单例(线程安全)

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

利用static域也可以达到线程安全的目的,但是这种方式导致无法按需创建单例对象。static域会随着类的初始化而初始化,故当类被JVM加载时,这个static域就会被初始化,从而保证了调用static方法之前就已经存在了实例,这保证了线程安全性。

5.使用内部类创建(线程安全)

public class Singleton5 {
	private Singleton5(){}
	private static class Holder{
		public static Singleton5 instance = new Singleton5();
	}
	
	public static Singleton5 getInstance(){
		return Holder.instance;
	}
}

这种方式在《Java并发编程实战》中称为“延长初始化占位类模式”,通过一个单独的内部静态类来初始化instance。JVM会推迟Holder的初始化操作,直到开始使用这个类时才会初始化,并且由于是通过静态变量初始化instance的,故不需要额外的同步,当其他类调用getInstance时,就会加载Holder,并初始化这个类、构造instance。这里的类内部static类不会在外部类被加载时就被初始化,只有第一次使用时才会被初始化(也就是调用外部的getInstance方法时)。

6.强烈不建议用的方式(双重检查加锁DCL、非多线程安全)

public class Singleton6 {
	private Singleton6() {}
	private static Singleton6 instance = null;

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

这种方式是通过在创建实例之前先检查对象是否存在,然后再在同步后继续判断对象是否存在,此时再进行创建新的对象。但是这种创建方式会存在如下问题:任何一个对象的创建都大致分为三个步骤——申请内存,初始化对象,对象指针引用对象,这之后就可以使用这个对象指针来引用这个对象了,但是,由于Java即时编译器存在指令重排的过程,故可能会导致创建步骤变为申请内存,对象指针引用对象,初始化对象。假设线程A申请内存,并将对象指针引用至当前内存,但是初始化过程相对复杂并耗时,线程B获取了这个对象的引用,检查不为null,就使用了这个对象(这个对象还正在进行初始化过程),从而导致了未定义的错误。故,不能使用这种方式进行编码,即使要使用,也要将instance定义为volatile。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值