YuHang’s Blog

设计模式(六)——单例模式

单例模式被使用于只允许有一个实例存在的需求中,通过将构造方法设为私有类型来避免无限制的new 新的对象

需求分析

为什么需要用单例模式呢?在开发中需要定义一些资源,这些资源是有固定数量的,比如说线程池,注册表。对于这些只能存在一个的对象就需要使用单例模式。当然我们可以使用静态来达到这样的需求,但是使用静态就意味着需要在程序一开始就将类加载进内存(而不是需要时加载)。

实例分析

经典版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
**
* Created by 宇航 on 2017/2/28.
* 需求:创建一个注册表对象,保证它只能有一个实例
*/
public class ClassicsSingleton {
public static void main(String[] args) {
Register register = Register.createRegister();
register.setId(3);
register = Register.createRegister(); //因为已经实例化,所以构造方法不会被再次调用
System.out.println(register.getId());
}
}
class Register {
private static Register register;
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
//设为private之后,只能在自身内部进行调用
private Register() {
System.out.println("可以将Register进行初始化");
}
//使用public的方法作为实例创建的出口
public static Register createRegister(){
if (register == null) {
return register = new Register();
}else {
return register;
}
}
}

脑洞版本

**

  • 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关键字

1
2
3
4
5
6
7
8
9
10
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton createInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

使用synchronized关键字可以完成线程的同步,但是会牺牲效率
2.
提前进行类初始化

1
2
3
4
5
6
7
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton createInstance() {
return instance;
}
}

这是当Singleton类进行装载的时候就会创建这个类,看起来我们再调用其他的静态方法可能提前加载对象,不过影响不大
3.
双重加锁检验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton {
private volatile static Singleton instance;
private Singleton (){}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

这个方法很好,这是有点麻烦。。而且因为volatile关键字是jsk1.5之后才存在的,所以不能兼容早期版本。