读书笔记:七种单例模式

1 饿汉式

public final class Singleton {
	
	// 实际变量
	private byte[] data = new byte[1024];
	
	// 在定义实例变量的时候直接初始化
	private static Singleton instance = new Singleton();
	
    // 私有构造函数,不允许外部new
	private Singleton() {}
	
	public static Singleton getInstance() {
		return instance;
	}
}

饿汉式的关键在于instance作为类变量并且直接得到了初始化,如果主动使用Singleton类,那么instance实例将会直接完成创建,包括其中的实例变量都会得到初始化,比如1K空间的data将会同时被创建。

但是instanceClassLoader加载后可能很长一段时间才被使用,那就意味着instance实例所开辟的堆内存会驻留更久的时间。如果一个类中的成员属性比较少,且占用的内存资源不多,饿汉的方式也未尝不可,相反,如果一个类中的成员都是比较重的资源,那么这种方式就会有些不妥。

总结起来,饿汉式的单例设计模式可以保证多个线程下的唯一实例,getInstance方法性能也比较高,但是无法进行懒加载。

2 懒汉式

public final class Singleton {

	// 实际变量
	private byte[] data = new byte[1024];

	// 在定义实例变量的时候直接初始化
	private static Singleton instance = null;

	private Singleton() {}

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

Singleton的类变量instance=null,因此当Singleton.class被初始化的时候instance并不会被实例化,在getInstance方法中会判断instance实例是否被实例化,看起来没有什么问题,但是将getInstance方法放在多线程环境下进行分析,线程不安全

image-20210227023110898

如图所示,两个线程同时看到instance==null,那么instance将无法保证单例的唯一性。

3 懒汉式+同步方法

public final class Singleton {

	// 实际变量
	private byte[] data = new byte[1024];

	// 在定义实例变量的时候直接初始化
	private static Singleton instance = null;

	private Singleton() {}

    // 向getInstance方法加入同步控制,每次只能有一个线程能够进入
	public static synchronized Singleton getInstance() {
		if (null == instance) {
			instance = new Singleton();
		}
		return instance;
	}
}

采用懒汉式+数据同步的方式既满足了懒加载又能够百分之百地保证instance实例的唯一性,但是**synchronized关键字天生的排他性导致了getInstance方法只能在同一时刻被一个线程所访问,性能低下。**

4 Double-Check

public final class Singleton {

	// 实际变量
	private byte[] data = new byte[1024];

	// 在定义实例变量的时候直接初始化
	private static Singleton instance = null;

	Connection conn;

	Socket socket;

	private Singleton() {
		// this.conn初始化
		// this.socket初始化
	}

	public static Singleton getInstance() {
		// 当instance为null时,进入同步代码块
        //同时该判断避免了每次都需要进入同步代码块,可以提高效率
		if (null == instance) {
			synchronized (Singleton.class) {
				// 判断如果instance为Null时则创建
				if (null == instance) {
					instance = new Singleton();
				}
			}
		}
		return instance;
	}
}

当两个线程发现null==instance成立时,只有一个线程有资格进入同步代码块,完成对instance的实例化,随后的线程发现null==instance不成立则无须进行任何动作,以后对getInstance的访问就不需要数据同步的保护了。

这种方式看起来是那么的完美和巧妙,既满足了懒加载,又保证了instance实例的唯一性,Double-Check的方式提供了高效的数据同步策略,可以允许多个线程同时对getInstance进行访问,但是这种方式在多线程的情况下有可能会引起空指针异常

Singleton的构造函数中,需要分别实例化connsocket两个资源,还有Singleton自身,根据JVM运行时指令重排序和Happens-Before规则,这三者之间的实例化顺序并无前后关系的约束,那么极有可能是 instance最先被实例化,而connsocket并未完成实例化,未完成初始化的实例调用其方法将会抛出空指针异常

image-20210227024407601

5 Volatile + Double-Check

Double-Check虽然是一种巧妙的程序设计,但是有可能会引起类成员变量的实例化connsocket发生在 instance实例化之后,这一切均是由于JVM在运行时指令重排序所导致的,而volatile关键字则可以防止这种重排序的发生,因此代码稍作修改即可满足多线程下的单例、懒加载以及获取实例的高效性,代码修改如下:

private volatile static Singleton instance = null;

6 Holder方式

public final class Singleton {

	// 实际变量
	private byte[] data = new byte[1024];
	
	private Singleton() {

	}
	
	// 在静态内部类中持有Singleton的实例,并且可被直接初始化
	private static class Holder {
		private static Singleton instance = new Singleton();
	}

	// 调用getInstance方法,事实上获得Holder的instance静态属性
	public static Singleton getInstance() {
		return Holder.instance;
	}
}

Singleton类中并没有instance的静态成员,而是将其放到了静态内部类Holder之中,因此在Singleton类的初始化过程中并不会创建Singleton的实例,Holder类中定义了Singleton的静态变量,并且直接进行了实例化,当Holder被主动引用的时候则会创建Singleton的实例,Singleton实例的创建过程在Java程序编译时期收集至<clinit>()方法中,该方法又是同步方法,同步方法可以保证内存的可见性、JVM指令的顺序性和原子性、Holder方式的单例设计是最好的设计之一,也是目前使用比较广的设计之一。

7 枚举方法

使用枚举的方式实现单例模式是《Effective Java》作者力推的方式,在很多优秀的开源代码中经常可以看到使用枚举方式实现单例模式的(身影),枚举类型不允许被继承,同样是线程安全的且只能被实例化一次,但是枚举类型不能够懒加载,对Singleton主动使用,比如调用其中的静态方法则INSTANCE会立即得到实例化

public enum Singleton {

	INSTANCE;

	private byte[] data = new byte[1024];

	Singleton() {
		System.out.println("INSTANCE will be initialized immediately");
	}

	public static void method() {
		// 调用该方法则会主动使用Singleton,INSTANCE将会被实例化
		System.out.println("run method");
	}

	public static Singleton getInstance() {
		return INSTANCE;
	}

	public static void main(String[] args) {
		Singleton.method();
	}
}

/*
INSTANCE will be initialized immediately
run method
*/

7.1 增加懒加载特性

public class Singleton {
	

	private byte[] data = new byte[1024];

	private Singleton() { }

	// 使用枚举充当holder
	private enum EnumHolder {
		INSTANCE;
		
		private Singleton instance;
	
		EnumHolder() {
			this.instance = new Singleton();
		}
		
		private Singleton getSingleton() {
			return instance;
		}
	}
	
	public static Singleton getInstance() {
		return EnumHolder.INSTANCE.getSingleton();
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值