单例模式 整理

近期在研究单例模式,参考了网上许多前辈的心得,做一些简单整理

第一种 : 饿汉模式

class  Singleton2{
	private final static Singleton2 INSTANCE = new Singleton2();
	private Singleton2(){}
	public static Singleton2 getInstance(){
		return INSTANCE;
	}
}

 

优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。

缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

第二种:懒汉模式

class Singleton4 {
	private static  Singleton4 instance;
	private Singleton4 (){}
	public static Singleton4 getTestClass (){
		if( instance == null ) {
			instance = new Singleton4();
		}
		return instance;
	}
}

这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。所以在多线程环境下不可使用这种方式。

两者比较:饿汉在类加载时候就初始化,而且由于基于classload机制,所以这种是线程安全,

             懒汉则是在调用时候才产生实例,而且还是线程不安全

 

还有一个final修饰符区别,由于final修饰需要初始化,所以懒汉模式是没有final修饰,饿汉模式有final修饰,有这个final修饰的饿汉模式更安全,这是因为 Final 变量在并发当中,有点类似volatite的功能,原理是通过禁止cpu的指令集重排序,来提供现成的可见性,来保证对象的安全发布,防止对象引用被其他线程在对象被完全构造完成前拿到并使用,, 这个又涉及对象初始化过程,一个对象初始化可以

 

Singleton2 INSTANCE = new Singleton2()

这个操作不是原子操作,可以拆成:
1、栈内存开辟空间给INSTANCE引用
2、堆内存开辟空间准备初始化对象
3、初始化对象
4、栈中引用指向这个堆内存空间地址

这个操作指令重排序之后可能会是1 2 4 3 ,这对应单个线程来说效果是一样,所以JVM认为是合法的,但是多线程环境会出问题,由于步骤3初始化还在步骤4引用指向内存地址后,会造成NullPointException,因为对象还没初始化指向的是一个null

volatile的作用:

保证内存可见性,防止指令重排序,并不保证操作原子性。这里用到的就是防止指令重排序的性质。

如何实现这些性质的  保证可见性:使用该变量必须重新去主内存读取,修改了该变量必须立刻刷新主内存。防止重排序:通过插入内存屏障。不保证操作原子性不保证操作原子性不保证操作原子性

正是由于volatile的这个作用,可以写出第三种单例模式 双重加锁 懒汉模式

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

这个模式就既保证了线程安全性也保证了lazy loading效果,而且效率较高,之所以双重加锁,是因为多线程环境下,A线程通过第一次null判断获得同步锁synchronized,在其初始化对象的时候,B线程此时由于对象还在初始化,所以也可以通过null判断等待A线程初始化对象后释放锁,当A线程初始化对象后,由于B线程已经通过null判断,所以可以继续获得synchronized同步锁并再次初始化一次实例,这样就造成多个实例

 

第四种:静态内部类

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

这个模式和饿汉模式的机制类似,两者都是采用类加载机制来保证初始化实例的时候只有一个线程,但两者又有不同,饿汉模式是在类装载的时候就初始化实例对象,没有lazy loading效果,而静态内部类却是在类装载完后,第一次调用静态内部类的时候才会初始化,即在调用getInstance方法时候才会初始化对象,而不是类第一次装载的时候

静态内部类和其他static修饰的静态变量,静态对象,静态块不一样,不是在类第一次初始化的时候加载的,而是和非静态内部类一样是在第一次调用的时候才加载

第五种:枚举

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值