java设计模式之单例模式完整版

472 阅读3分钟

单例模式的核心就是只能有一个实例

所以没什么好说的 构造方法不能公开 ,不然谁都能创建实例对象了---直接private

第一种平平无奇

直接再类加载的时候初始化对象

好处就很明显,首先简单,不涉及并发安全问题,因为类只加载一次,而且实例是再类加载的时候就被加载了,不管你用没用,这就是坏处,但是我觉的无伤大雅,类总是要用的 不然你加载他干嘛

public class Single01 {
	private static final Single01 INSTANCE=new Single01();

	/**
	 * 私有化
	 */
	private Single01(){}
	public static Single01 getInstance(){
		return INSTANCE;
	}


}

当然这样也是可以的,利用static代码块,再程序初始化的时候加载。

public class Single01 {
	private static final Single01 INSTANCE;
	static {
		INSTANCE=new Single01();
	}

	/**
	 * 私有化
	 */
	private Single01(){}
	public static Single01 getInstance(){
		return INSTANCE;
	}


}

第二中开始着手解决问题我不想直接加载类

当调用的时候再加载就好了....

public class Single02 {
	private Single02(){}
	private static Single02 INSTANCE;

	public static Single02 getINSTANCE() {
		if (INSTANCE==null){
			INSTANCE=new Single02();
			return INSTANCE;
		}
		return INSTANCE;
	}

	public static void main(String[] args) {
		for (int i=0;i<100;i++){
			new Thread(()->{
				System.out.println(Single02.getINSTANCE().hashCode());
			}).start();
		}
	}
}

当我运行程序时,好像结构也没啥问题,是因为线程执行的太快了

image.png

但是实际上问题大了,因为线程可以抢在if后同时生产实例,那么问题就很麻烦了, 举个例子。

public class Single02 {
	private Single02(){}
	private static Single02 INSTANCE;

	public static Single02 getINSTANCE() {
		if (INSTANCE==null){
			try {
				Thread.sleep(1000);
			}catch (Exception e){
				e.printStackTrace();
			}
			INSTANCE=new Single02();
			return INSTANCE;
		}
		return INSTANCE;
	}

	public static void main(String[] args) {
		for (int i=0;i<100;i++){
			new Thread(()->{
				System.out.println(Single02.getINSTANCE().hashCode());
			}).start();
		}
	}
}

再原来的基础上,我继续加了sleep 为了让更多的线程进来,有机可趁。结果

image.png

就没几个一样的哎,还不如第一个呢 ,我比较推荐第一个。

但这个是可以改进的加个锁嘛

public synchronized static Single02 getINSTANCE() {
		if (INSTANCE==null){
			try {
				Thread.sleep(1000);
			}catch (Exception e){
				e.printStackTrace();
			}
			INSTANCE=new Single02();
			return INSTANCE;
		}
		return INSTANCE;
	}

加了锁以后 结果就一样了

image.png

但是效率太低了,整个太拉跨了。可以尝试再代码块上加锁。

public  static Single02 getINSTANCE() {
		if (INSTANCE==null){
			try {
				Thread.sleep(1000);
			}catch (Exception e){
				e.printStackTrace();
			}
			synchronized (Single02.class){
				INSTANCE=new Single02();
					return INSTANCE;
//				if (INSTANCE==null){
//					INSTANCE=new Single02();
//					return INSTANCE;
//				}
			}
//			try {
//				Thread.sleep(1000);
//			}catch (Exception e){
//				e.printStackTrace();
//			}

		}
		return INSTANCE;
	}

这个代码输出是有问题的,因为锁至少保证一次只运行一个,但是我们的要求是创建实例代码只执行一次,所以要加二次判断,不然的话,还是出问题

image.png

现在加上二次判断

	public  static Single02 getINSTANCE() {
		if (INSTANCE==null){
			try {
				Thread.sleep(1000);
			}catch (Exception e){
				e.printStackTrace();
			}
			synchronized (Single02.class){
				
				if (INSTANCE==null){
					INSTANCE=new Single02();
					return INSTANCE;
				}
			}

		}
		return INSTANCE;
	}

可能你会觉得第一次判断没用用,但实际上很有用,可以筛选掉大部分的线程,不然去拿锁, 还有一点要提醒的是变量INSTANCE要加上volatile 不然又可能编译本地代码的时候会乱序,可以回执行出两个实例。

还有一种比较完美的方法

利用内部类完美解决懒加载,因为静态内部类使用他的时候在加载

public class Single03 {

	private Single03(){}
	static class SingleHolder{
   private static final Single03 INSTANCE=new Single03();
		public static Single03 getInstance(){


			return INSTANCE;
		}
	}

那么完美就可以在内部类里面做第一种的事情就好了。

还有一种方法就比较有趣了

public enum Single04 {
	INSTANCE
}

对 你没看错 就是一行代码就好了, 且这个还是private static final的,且不能被反序列化,不会被反射搞到手,因为枚举类没用构造方法,且他还帮你实例化了 你说爽不爽