欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

单例模式的五种写法

程序员文章站 2022-07-13 23:38:10
...

单例模式

创建唯一类、唯一的对象、有唯一的全局访问点

1,五种写法

1)懒汉式

代码

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

效果1:线程不安全

原因:多个线程并行调用getinstance()的时候,会创建多个实例

解决方案1:将整个getinstance()方法设为同步(synchronized)

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

效果2:做到了线程安全,解决了多实例问题。但不高效,因为任何时候只能有一个线程调用getinstance()方法,但同步操作是只需要在第一次调用时才被需要,也就是说第一次创建实例对象的时候,也就是说当有多个线程时,同步操作对第2个线程始是不起作用的。因此可引出双重检验锁来解决这个问题。

2)双重检验锁

简单来说,就是一种使用同步块加锁的方法,称其为双重检验锁,是因为它有两次检查instance=null ,一次是在同步块外,一次是在同步块内,在同步块内检查就是为了防止多个线程一起进入同步块外的if里,从而造成多个实例的问题。

public static Singleton getSingleton(){
		if(instance == null){
		   synchronized(Singleton.class){
			  if(instance == null){
				instance = new Singleton();
			    }
			}
		}
		return instance;
}

效果3:仍在instance = new Singleton()这里存在一个问题,这并非是一个原子操作,实际JVM进行了3件事。也就是给instance分配内存、调用Singleton的构造函数来初始化成员变量、将instance对象指向分配的内存空间(执行完这步后,instance就为非null了)。但因为JVM的即时编译器中存在指令重排序的优化,所以第2和第3步的执行顺序是不能保证的。当3先执行完后,instance 已经是非空了,但没初始化 ,所以第二个线程将会直接返回instance,然后使用。因此存在多线程问题.

解决方案2:将instance变量声明成volatile即可,volatile具有可见性,在这可以解决JVM的指令重排序优化问题,volatile是禁止这个的。也就是volatile变量的赋值操作后面会有一个内存屏障,读操作不会被重排序到内存屏障之前。

public class Singleton(){
		private volatile static Singleton instance; 
		private Singleton(){}
		
		public static Singleton getSingleton(){
			 if(instance == null){
				
				synchronized(Singleton.class){
					if(instance == null){
						instance = new Singleton();
					}
				}
			}
				
			return instance;
		}
}

效果4:volatile只有在java 5中及之后才可避免JVM重排序问题。因此推出了比较简单的实现线程安全的单例模式的方法,让我们继续往下说。

 

3)饿汉式

public class Singleton(){
		//类加载时就初始化
		private static final Singleton instance = new Singleton();
		
		private Singleton(){}
		
		public static Singleton getInstance(){
			return instance;
		}
}

效果:单例的实例被声明了static和final变量,在第一次加载类到内存中时就会初始化,所以创建实例本身是线程安全的。但这不是懒加载模式,因为会在加载类后就初始化,就算客户端没调用getInstance()方法。

4)静态内部类(最好的)

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

5)枚举(非常简单)

public enum EasySingleton{
		INSTANCE;
}

效果:不用担心双重检验锁问题,也能防止反序列化导致重新创建新的对象

相关标签: java学习