单例模式被使用于只允许有一个实例存在的需求中,通过将构造方法设为私有类型来避免无限制的new 新的对象
需求分析
为什么需要用单例模式呢?在开发中需要定义一些资源,这些资源是有固定数量的,比如说线程池,注册表。对于这些只能存在一个的对象就需要使用单例模式。当然我们可以使用静态来达到这样的需求,但是使用静态就意味着需要在程序一开始就将类加载进内存(而不是需要时加载)。
实例分析
经典版本
|
|
脑洞版本
**
- Created by 宇航 on 2017/2/28.
- 需求:我们决定建立一个全球限量五部的超级跑车,请你为我们的生产线设计代码
- 当跑车数量多于三部时拒绝生产并发出警告
*/
public class SingletonTest {
public static void main(String[] args) {
// CarFactory carFactory = new CarFactory(); 无法实例化
for (int i = 0; i < 5; i++) {
}CarFactory.createCar();
}
}
class CarFactory{
private static int count = 0;
private CarFactory(){
}
public static CarFactory createCar(){
if (count < 3){
System.out.println("创建了一辆车");
count++;
return new CarFactory();
}else {
System.out.println("生产已达上限");
return null;
}
}
}
回顾一下以上两种模式有什么弊端
基于经典单例模式有一个弊端:多线程
上面两种编程方式是线程不安全的。假设一种情况:当线程a运行到if (register == null)
的情况时,下一个时间线程b也运行到这一句判断,这是因为线程a还没有new 一个新的对象,所以两个线程判断这个条件都为true。糟糕的事情发生了——我们创建了两个对象,对于顾客来说他们辛辛苦苦积累的数据被恢复成最开始的模样了。
解决方法
1.
使用synchronized关键字
使用synchronized关键字可以完成线程的同步,但是会牺牲效率
2.
提前进行类初始化
这是当Singleton类进行装载的时候就会创建这个类,看起来我们再调用其他的静态方法可能提前加载对象,不过影响不大
3.
双重加锁检验
这个方法很好,这是有点麻烦。。而且因为volatile关键字是jsk1.5之后才存在的,所以不能兼容早期版本。