设计模式之单例模式

单例模式

 

< 单例模式简而言之就是某各类只能被实例化一次 ,JVM中不能存在两个对象 。 实现该模式的方式也比较简单,就是将类的构造器设置成私有的,然后提供一个公开的方法获取该唯一的实例  ,以下是具体的单例类实现方式>

 

第一种实现方式

public class Singleton {
	private static Singleton single;
	
    //私有构造器阻止外部类的随意创建
	private Singleton () {
	}
	
    //提供一个公开的方法访问实例
	public static Singleton getInstance() {
        if(single == null){           //1
            single = new Singleton(); //2
        }
		return single;
	}
}

这种方式存在一个缺点是线程不安全。想想当两个线程都调用getInstance方法时,都通过了1处代码且没有执行2处代码。当他们执行2处代码时将会创建两个实例,违背了单例类的初中。所以这里我们进行改进一下给方法加上锁。

public class Singleton {
        private static Singleton single;
	
        //私有构造器阻止外部类的随意创建
	private Singleton () {
	}
	
        //提供一个公开的方法访问实例
	public synchronized static Singleton getInstance() {
            if(single == null){           
                single = new Singleton();
            }
		    return single;
	}
}

确实改进之后线程安全了,也不会存在创建两个实例对象的风险了。但是这又会存在一个新的问题就是效率问题。我们都知道在Java中对方法进行加锁之后系统性能会降低很多。而且这里给方法加锁只是为了避免第一次单例对象为null时出现的错误,一旦单例对象被初始化就不会出现线程不安全的问题了,只使得这个加锁动作是多余的(因为只有在第一次创建的时候会有帮助,之后就基本是累赘了)。于是我们又进行新一轮的改进,将锁加在if语句里面而不是方法上,变成这样。

public class Singleton {
        private static Singleton single;
	
        //私有构造器阻止外部类的随意创建
	private Singleton () {
	}
	
        //提供一个公开的方法访问实例
	public static Singleton getInstance() {
            if(single == null){                    //1
                synchronized(Singleton.class){     //2    
                    single = new Singleton();
                }
            }
            return single;
	}
}

这样做是否正确呢?这样是存在问题的!当我们两个以上的线程都通过1处的判断时尽管对Singleton加锁了,但是还是会创建两个实例对象,所以我们必须还要进行一次判断,变成这样。

public class Singleton {
        private static Singleton single;
	
        //私有构造器阻止外部类的随意创建
	private Singleton () {
	}
	
        //提供一个公开的方法访问实例
	public static Singleton getInstance() {
            if(single == null){                    //1
                synchronized(Singleton.class){      
                    if(single == null){            //2          
                        single = new Singleton();  //3
                    }
                }
            }
            return single;
	}
}

这样问题就解决了,即使多个线程进入1处,但是2处将还会进行一次检查,所以不存在创建多个个实例的风险了,这种方式双重检查。所以到这里你以为真的就没问题了吗?错(太年起)!

由于这里涉及很多底层知识,我们简单讲一下存在的问题。由于创建一个实例对象可分为多个步骤(即3处代码),可能要检查类加载器, 分配内存,调用构造器,赋值给引用变量等,这些步骤顺序执行当然没问题 , 但是Cpu由于要优化性能,可能会对指令进行重排序,即打乱这些指令的步骤,可能引用对象还没有初始化就被赋值了,而此时可能切换到另外一个调用该方法的线程中去,该线程正在执行1处代码,发现single不为空,于是返回一个还没有初始化的实例对象。对于这种情况我们只能通过给Singleton 加上volatile关键字来防止命令的重排序。

public class Singleton {
        private static volatile Singleton single;
	
        //私有构造器阻止外部类的随意创建
	private Singleton () {
	}
	
        //提供一个公开的方法访问实例
	public static Singleton getInstance() {
            if(single == null){                    //1
                synchronized(Singleton.class){      
                    if(single == null){            //2          
                        single = new Singleton();  //3
                    }
                }
            }
            return single;
	}
}

到这里所有问题都解决了,实现了一个延时加载线程安全的双层检(double check)查的单例模式。

 

 

第二种实现方式

更为简单且不存在线程安全问题,也不需要双层检查。如下代码:

public class Singleton {
	private static Singleton single = new Singleton();
	
    //私有构造器阻止外部类的随意创建
	private Singleton() {
	}
	
    //提供一个公开的方法访问实例
	public static Singleton getInstance() {
		return single;
	}
}

该方法又叫饿汉模式,在一开始就将实例对象创建好了。该方法唯一缺点就是开始未被调用时该实例占用了内存资源。

 

 

第三种实现方式

Java给我们实现的,又叫天然的单例类。

public enum EnumSingleton {
	
	/**
	 *  天然单例类 
	 */
	INSTANCE;
	
	private EnumSingleton() {
	}
	
	public static EnumSingleton getInstance() {
		return INSTANCE;
	}
}

采用java提供的枚举类型实现天然单例类,和第二中原理差不多。

 

 

 

第四种实现方式

public class StaticInnerSingleton {
	
	/**
	 * 延时加载 内部类(线程安全)
	 * @author lenovo
	 *
	 */
	public static class StaticInner{
		private static StaticInnerSingleton single = new StaticInnerSingleton();
	}
	
	private StaticInnerSingleton() {
	}
	
	public static StaticInnerSingleton getInstance() {
		return StaticInner.single;
	}
}

该方式采用了内部类是延时加载的机制来设计的,也就是说该静态内部类只有在第一次被调用是才会加载。

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值