java中单例模式的实现

实现单例

单例模式是:确保一个类只有一个实例,并提供全局唯一访问点。
唯一访问点需要把构造方法私有,提供获取实例的公共方法
全局都是一个实例:实例的对象必须是static

基于以上几点,可以写出基本懒汉式和饿汉式

一、饿汉式
public class FirstSingle {
//饿汉式  类加载就创建了对象
private FirstSingle() {
}
private static FirstSingle single = new FirstSingle();

public static FirstSingle getSingle() {
	return single;
}
}

二、懒汉式 延时使用时创建对象
public class FirstSingle {
//懒汉式  延时使用时创建对象
private FirstSingle() {
	
}
private static FirstSingle single = null;

public static FirstSingle getSingle() {
	//保持外部获取时,只创建对象一次 ,使用同一个对象。若不判断,每次外部获取都会新建对象,与以前的不是同一个对象,违背了类只有一个实例原则!
	if(single == null)
		single = new FirstSingle();
	return single;
	//赖汉局限:多线程下if语句判断不安全
}
}

三、 懒汉式多线程下优化 -> 双重校验式

①既然上述的懒汉式是线程不安全的,那就加锁使之变成线程安全

public class FirstSingle {
//既然上述的懒汉式是线程不安全的,那就加锁使之变成线程安全
private FirstSingle() {
	
}
private static FirstSingle single = null;

public synchronized static FirstSingle getSingle() {
	if(single == null)
		single = new FirstSingle();
	return single;
	//加同步赖汉局限:多线程下某一时间点只能一个线程访问,其他线程等待被阻塞时间过长,即使其他线程中的single已经被实例了,效率堪忧
}
}

加同步赖汉局限:多线程下某一时间点只能一个线程访问,其他线程等待被阻塞时间过长,即使其他线程中的single已经被实例了,效率堪忧

②进一步优化,既然上面有线程以及获取了single实例后的对象,所以根本不需要再进入if判断中,因此可以将同步锁的范围由整个方法缩小至if处

public class FirstSingle {
//进一步优化,既然上面有线程以及获取了single实例后的对象,所以根本不需要再进入if判断中,因此可以将同步锁的范围由整个方法缩小至if处
private FirstSingle() {
	
}
private static FirstSingle single = null;

public  static FirstSingle getSingle() {
	
	if(single == null)
		synchronized(FirstSingle.class) {
			//不过这时又出现新的问题。假设两个线程T1、T2都是没有得到single的实例。
			//1.T1先进入同步块后,T2进入if在同步块前等待了。
			//2.T1完成实例化,退出同步块释放同步锁,return返回
			//3.此刻因T2已经老早进入if中,不知道single已经被实例化,所以T2也进入同步块又实例化一遍,返回。这样造成两次getSingle的并不是统一对象!
			//其根本原因是T2进入同步块中没有在一次判断是否single已经实例过!
		single = new FirstSingle();
		}
	return single;
}
}

不过这时又出现新的问题。假设两个线程T1、T2都是没有得到single的实例。
1.T1先进入同步块后,T2进入if在同步块前等待了。
2.T1完成实例化,退出同步块释放同步锁,return返回
3.此刻因T2已经老早进入if中,不知道single已经被实例化,所以T2也进入同步块又实例化一遍,返回。这样造成两次getSingle的并不是统一对象!
其根本原因是T2进入同步块中没有在一次判断是否single已经实例过!

③既然上面分析 T2进入同步块,没有判断上次退出同步其他线程创建了实例。那加上判断是否可行

public class FirstSingle {
//既然上面分析 T2进入同步块,没有判断上次退出同步其他线程创建了实例。那加上判断是否可行
private FirstSingle() {
	
}
private static FirstSingle single = null;

public  static FirstSingle getSingle() {
	
	if(single == null)
		synchronized(FirstSingle.class) {
			if(single == null)
				//这里加上if是解决了上面的问题,
				//但是还有考虑JVM指令重排的特性,低概率的同步错误出现,多线程下可能会有某线程得到没有初始化的实例。
				//single = new FirstSingle(); 会有三步过程:
					//1.为new FirstSingle()实例对象申请空间,对象的字段设置为默认值 0值
					//2.初始化实例对象
					//3.single指向实例的对象
				//指令重排后,1—>3—>2, T1 完成1,3 ,释放同步锁,T1还未完成2,没有return;T2进入同步块发现single不是null,返回single这个引用,但是2实例会、化对象还没有完成,出现了错误。
				//若T1在T2返回前完成了第2步,那T2返回也就是正确的对象。
				
				single = new FirstSingle();
		}
	return single;
}
}

④既然有指令重排,那就不让他指令重排,jdk提供volatile关键字。终极版如下 双重校验锁式

public class FirstSingle {
//既然有指令重排,那就不让他指令重排,jdk提供volatile关键字。终极版如下
private FirstSingle() {
	
}
// volatile 保证每个线程得到singlon值是最新的,可见性、防止jvm优化指令重排
private static volatile FirstSingle single = null;

public  static FirstSingle getSingle() {
	//避免线程已经得到实例都进入同步块
	if(single == null)
		synchronized(FirstSingle.class) {
			//第二个if 对于多个没有实例化的线程,避免在进入同步块之前其他线程已经实例过,防止重复实例
			if(single == null)
				//new 这句 加上volatile防止jvm优化指令重排,通知其他线程single的可见,避免出现上面不加volatile时的同步错误
				single = new FirstSingle();
		}
	return single;
}
}

四、静态内部类

利用私有的静态内部类防止外界访问且,因静态全部外部类对象只有一份内部类,内部类用以创建对象,符合了单例的全局只有一个对象。再上提供公共方法获取实例单例,符合提供全局的访问点。

优势:外部类加载时,内部类不会加载,内部不加载就不new,只有在内部类被引用,即调用getInstance时导致JVM加载内部类
优势:线程安全性,和延迟加载单例的实例化

package designpatten.singleton;

//静态内部类方式 实现单例
public class SingletonInnerClass {

private SingletonInnerClass() {}
//私有静态内部类 防止外界访问且全部外部类对象只有一份内部类,

private static class SingletonHolder {
	 static SingletonInnerClass single = new SingletonInnerClass(); 
}

public SingletonInnerClass getInstance() {
	return SingletonHolder.single;
}
}
//保证了单例的唯一性:在第一次创建单例后,该外部类的在方法去的常量池已经存在,再下一次getInstance会把single的符号引用直接变成直接引用去找常量池的单例
//所以在除第一初始单例外,其他调用getInstance会直接返回单例对象,这里像是饿汉式

//线程安全性:jvm会保证在进行类加载时<clinit>会加载类的static部分,当然这里如加载外部类不会主动加载静态内部类,
//因为jvm有且仅有的5中情况对类初始化中没有对静态内部类初始,所以它是被动初始的.
//jvm保证一个线程在初始化类期间是线程安全的,也就说其他线程若要初始同一个会被阻塞。这点保证了在线程调用getInstance初始内部类是线程安全

	类加载时机:JAVA虚拟机在有且仅有的5种场景下会对类进行初始化。
1.遇到new、getstatic、setstatic或者invokestatic这4个字节码指令时,对应的java代码场景为:new一个关键字或者一个实例化对象时、读取或设置一个静态字段时(final修饰、已在编译期把结果放入常量池的除外)、调用一个类的静态方法时。
2.使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没进行初始化,需要先调用其初始化方法进行初始化。
3.当初始化一个类时,如果其父类还未进行初始化,会先触发其父类的初始化。
4.当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的类),虚拟机会先初始化这个类。
5.当使用JDK 1.7等动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

这5种情况被称为是类的主动引用,

虚拟机会保证一个类的()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值