什么是单例模式
保证一个类只有一个实例,并且提供一个访问该实例的全局入口。
饿汉式
在类加载的时候就立即初始化并且创建单例对象。
public class HungrySingle {
// final对象创建后就不可变
private static final HungrySingle single = new HungrySingle();
// 构造器私有,防止外部创建对象
private HungrySingle() {
}
public static HungrySingle getInstance(){
return single;
}
}
优点:没有加任何的锁,效率比较高,在线程出现以前就已经实例化了是线程安全的。
缺点:类加载的时候就初始化,不管你是否使用都创建对象,有可能会存在资源浪费。不支持延迟加载。
懒汉式
在需要使用的时候才进行实例化,延迟加载。
public class LazySingle {
private static LazySingle single = null;
// 构造器私有,防止外部调用
private LazySingle() {
}
public static LazySingle getInstance(){
if (single == null) {
single = new LazySingle();
}
return single;
}
}
优点:延迟加载。
缺点:在高并发的情况存在线程安全问题,可能有多个线程进入到if条件里面。懒汉模式怎么实现线程安全,可以在getInstance方法上加上synchronized锁。
双重检验模式
懒汉模式的改进,通过双重检验减少了不必要的同步提高了性能。但是在高并发的情况下存在指令重排的问题。
public class LazySingle {
private volatile static LazySingle single = null; // 1、volatile修饰
private LazySingle() {
}
public static LazySingle getInstance(){
if(single==null){ //2:减少不要同步,优化性能
synchronized (LazySingle.class){ // 3:同步,线程安全
if(single==null){
single = new LazySingle(); //4:创建singleton 对象
}
}
}
return single;
}
}
我们可以看到双重检验模式在懒汉是的基础上,改成了双重判空检验。加入有A、B两个线程,同时通过了第一个判空检验,假设A先获取到锁,那么B线程进行等待,A线程进入锁内部,此时single对象为空,所以创建对象然后释放锁,返回对象。然后B线程获取到锁,进入锁内部,但是由于A线程已经创建了single对象,所以B线程什么都不做,之间返回对象,从而保证线程安全。
为什么我们要在single变量上面加上volatile呢?
因为虽然我们已经使用synchronized进行同步,但在第4步创建对象时,会有下面的伪代码:
memory=allocate(); //1:分配内存空间
ctorInstance(); //2:初始化对象
singleton=memory; //3:设置singleton指向刚排序的内存空间
当线程A在执行上面伪代码时,2和3可能会发生重排序,因为重排序并不影响运行结果,还可以提升性能,所以JVM是允许的。如果此时伪代码发生重排序,步骤变为1->3->2,线程A执行到第3步时,线程B调用getsingleton方法,在判断singleton==null时不为null,则返回singleton。但此时singleton并还没初始化完毕,线程B访问的将是个还没初始化完毕的对象。当声明对象的引用为volatile后,伪代码的2、3的重排序在多线程中将被禁止!
静态内部类模式
和饿汉式一夜同样利用类加载机制,保证线程安全。很好的将饿汉式和懒汉式结合在一起,既实现延迟加载,又保证了系统性能。
public class StaticSingle {
private StaticSingle(){}
private static class StaticSingleHolder{
private static StaticSingle single = new StaticSingle();
}
public static StaticSingle getInstance(){
return StaticSingleHolder.single;
}
}
从代码我们可以看到getInstance
方法通过调用StaticSingleHolder
静态内部类来返回StaticSingle
,这里为什么这么写呢?
首先我们需要明白的当StaticSingle
类加载的时候,StaticSingleHolder
静态内部类是没有被加载的(这里和静态变量在类加载的时候初始化不一样,请注意),静态内部类只有在getInstance
方法中被主动使用的时候才会被加载。
枚举模式
枚举模式有三个好处1.实例的创建线程安全,确保单例。2.防止被反射创建多个实例。3.没有序列化的问题。
public enum SingleEnum {
//实例化对象
INSTANCE;
/**
* 对象需要执行的功能
*/
void getInstance(){
}
}
反射/序列化,获取对象,以及防止方式
public class SingleLazy implements Serializable{
//提供静态的全局变量 作为访问该类实例的入口 但是这里不立即加载
private static SingleLazy sh = null;
/**
* 构造器私有 无法创建对象
*/
private SingleLazy(){
if(sh!=null){
throw new RuntimeException();
}
System.out.println("构造函数被调用了");
}
/**
* 对外提供get方法获取 该类的实例
* @return
* @throws InterruptedException
*/
public static synchronized SingleLazy getInstance() {
if(sh==null){
sh = new SingleLazy();
}
return sh;
}
private Object readResolve()throws ObjectStreamException{
return sh;
}
}
参库博客: