java之五种单例模式

对比

线程安全效率延迟加载
懒汉模式安全(方法加锁)低(方法加锁)能(对象空才实例化)
饿汉模式安全(初始化加载)高(无锁)不能(类初始化就已经加载)
DCL模式安全(代码块加锁)高(仅第一次加锁)能(对象空才实例化)
静态内部类模式安全(初始化加载)高(无锁)能(内部类加载才实例化)
枚举模式安全(jvm保证初始化加载)高(无锁)不能(枚举类加载就完成实例化)

懒汉模式

写法1

public class Singleton01 {

	//懒汉模式:线程安全,效率低,能实现延迟加载
	//构建单例对象,初始化放在静态方法中
	private static Singleton01 singleton = null;
	
	//构造器私有化
	private Singleton01(){};
	
	//静态方法初始化获取实例
	public static synchronized Singleton01 getInstance(){
		if(singleton == null){
			singleton = new Singleton01();
		}
		return singleton;
	}
}

写法2(此方法等同于下面的DCL)

public class Singleton01 {

	//懒汉模式:线程安全,效率低,能实现延迟加载
	//构建单例对象,初始化放在静态方法中
	private static Singleton01 singleton = null;
	
	//构造器私有化
	private Singleton01(){};
	
	//静态方法初始化获取实例
	public static synchronized Singleton01 getInstance(){
		if(singleton == null){
			singleton = new Singleton01();
		}
		return singleton;
	}
	
	private static synchronized void singletonInit(){
		if(singleton == null){
			singleton = new Singleton01();
		}
	}
	
	public static Singleton01 getInstance02(){
		if(singleton == null){
			singletonInit();
		}
		return singleton;
	}
}

特点

线程安全(方法有synchronized),调用效率不高(每次进方法都要获取锁),但是能实现延迟加载(getInstance中判空才会初始化)

饿汉模式

public class Singleton02 {

	//饿汉模式:线程安全,效率高,不能实现延迟加载
	//构建单例对象,直接类初始化一次
	private static Singleton02 singleton02 = new Singleton02();
	
	//构造器私有化
	private Singleton02(){};
	
	//静态方式直接获取
	public static Singleton02 getInstance(){
		return singleton02;
	}
}

特点

线程安全(类初始化实例对象就会创建),效率高(无锁),没有延迟加载(类初始化就创建了)

DCL(Double Check Lock)双重校验锁

public class Singleton03 {

	//双重校验锁
	private static volatile Singleton03 singleton03 = null;
	
	//构造器私有化
	private Singleton03(){};
	
	//静态方法获取实例
	public static Singleton03 getInstance(){
		//先判断实例是否为空
		if(singleton03 == null){
			//为空加锁
			synchronized(Singleton03.class){
				//再判断一次,有可能前面一次判断实例为空到加锁之前,
				//过程中有其他线程创建了对象,重新判空确保安全
				if(singleton03 == null){
					/*实例依旧为空则创建对象
					 * 但是由于JVM命令重排,new 一个对象有三步
					 * 1. 在堆空间分配内存
					 * 2. 将对象初始化(调用对象的构造器)
					 * 3. 将内存空间的地址赋值给对象的引用
					 * 由于JVM会将没有依赖的2,3两步重排执行(指的是步骤1分配内存必须在步骤3将内存地址赋值到引用之前,因此1,3两个步骤有依赖,但是步骤2跟步骤3则是没有依赖,JVM可能会重排执行),那么有可能出现先执行1,再执行3,再执行2;
					 * 这样就可能A线程执行1,在执行3的时候,B线程执行到了锁外面判空的地方,然后发现对象不为空(因为singleton03是static修饰的共享变量,B线程有可能看到对象被修改),直接返回对象
					 * 但是实际上对象还没有执行2步骤初始化,这样后面使用直接报错;
					 * 解决方法:将singleton03加上Volatile禁止命令重排(并非可见性)
					 */
					singleton03 = new Singleton03();
				}
			}
		}
		return singleton03;
	}
	
	
}

特点

  1. 实例对象如果不加volatile会出现不安全,原因:
    1. jvm自动命令重排
    2. 线程B拿到还未初始化但是有引用的实例对象,导致运行报错;
  2. 加了volatile之后,由于volatile禁止命令重排,上图中肯定是1,2,3顺序执行,则能保证线程安全,效率高(只有第一次初始化加锁),能实现延迟加载(需要的时候才初始化)

静态内部类

public class Singleton04 {

	//构造器私有化
	private Singleton04(){};
	
	//静态内部类(主类加载时不会加载静态内部类)
	private static class Instance{
		private static Singleton04 singleton04 =  new Singleton04();
	}
	
	//静态方法获取实例
	public static Singleton04 getInstance(){
		return Instance.singleton04;
	}
}

特点

  1. 为什么要使用静态内部类?因为静态内部类可以直接外部类打点调用初始化,如果是用普通内部类则需要创建外部类实例,但是这个外部类实例又要由内部类创建,因此使用静态内部类,那这样实例对象也得是静态的;
  2. 线程安全(只在内部类初始化一次),效率高(无锁),能实现延迟加载(外部类加载,内部类不会加载,只当使用到getInstance的时候才会加载)
  3. 但是其实说它完美,也不一定,如果在构造函数中抛出异常,实例将永远得不到创建,也会出错

枚举单例

public class Singleton05 {

	//构造器私有化
	private Singleton05(){};
	//构建静态枚举类
	private static enum Instance{
		//枚举对象
		INSTANCE;
		//实例对象
		private Singleton05 singleton05 = null;
		//枚举对象构造器有jvm保证只执行一遍
		private Instance(){
			singleton05 = new Singleton05();
		}
	}
	//静态方法获取实例
	public static Singleton05 getInstance(){
		return Instance.INSTANCE.singleton05;
	}
}

特点

线程安全(jvm保证实例只被初始化一次),效率高(无锁),不能实现延迟加载(枚举在加载的时候,实例对象就已经被初始化)

选择方式

  1. 单例对象小,不需要延时加载,枚举好于饿汉
  2. 单例对象大,需要延时加载,静态内部类好于懒汉
  3. DCL加volatile在jdk5之前可能仍然有问题,故不建议使用;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值