设计模式--单例模式

本以为单例模式就是简单的模式,非常清晰明了,今天跟老袁一聊天,让我更多的了解了它的精妙之处,以及要写出精妙的单例模式也是很有讲究。 🥕

想必大家都知道,单例模式为什么称之为单例模式。因为这个类只有一个实例,会自行实例化,并提供该实例。总结一下就是单例模式将会有以下特点:

  • 1、单例类只能有一个实例。
  • 2、单例类必须自己创建自己的唯一实例。
  • 3、单例类必须给所有其他对象提供这一实例。

饿汉式单例

单例模式必提的,饿汉懒汉,两大壮汉~

1
2
3
4
5
6
7
8
9
//饿汉式单例类.在类初始化时,已经自行实例化   
public class Singleton {
private Singleton() {}
private static final Singleton single = new Singleton();
//静态工厂方法
public static Singleton getInstance() {
return single;
}
}

这个类,一开始就生成了一个静态的对象供系统使用,就像一个饿的不行的壮汉冲进饭馆(系统加载)就迫不及待的开吃了(实例化对象),这个类以后也不会改变,是天生线程安全的。

懒汉式单例

第二个壮汉来了,不过他是个懒汉,懒洋洋的~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton1 {

private Singleton1(){}
//这里就不再定义final
private static Singleton1 single1 =null;

public static Singleton1 getInstance(){
if(single1==null){
single1 = new Singleton1();
}
return single1;
}

}

这个壮汉比较懒,他慢腾腾的在饭馆坐下了,等着朋友来喊他一起吃(在第一次调用的时候实例化自己)。
到这里我们一般都觉得把单例模式看的七七八八了,或许有厉害的兄弟觉得还缺点什么,是的。饿汉不比懒汉,他是不安全的。以前我也是只知道个不安全,知道要加个同步锁。今天经过老袁的一讲解,豁然开朗。

加上同步锁的 懒汉模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton2 {

private Singleton2(){}

private static Singleton2 single2 =null;

public synchronized static Singleton2 getInstance(){
if(single2==null){
single2 = new Singleton2();
}
return single2;
}

}

加上synchronized,我们确保了同步。但是要同步,就牺牲了性能,更何况我们在绝大部分情况下都是不需要同步的。于是乎,我们有了以下的方案。

双重检查锁定的 懒汉模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Singleton3 {

private Singleton3(){}

private static Singleton3 single3 =null;

public static Singleton3 getInstance(){
if(single3==null){
synchronized (Singleton3.class){ //1
if (single3 == null){ //2
single3 = new Singleton3(); //3
}
}
}
return single3;
}

}

双重检查锁定,在 //2 处的第二次检查,创建两个不同的 Singleton 对象成为不可能。
假设有下列事件序列:

  1. 线程 1 进入 getInstance() 方法。
  2. 由于 instance 为 null ,线程 1 在 //1 处进入 synchronized 块。
  3. 线程 1 被线程 2 预占。
  4. 线程 2 进入 getInstance() 方法。
  5. 由于 instance 仍旧为 null ,线程 2 试图获取 //1 处的锁。然而,由于线程 1 持有该锁,线程 2 在 //1 处阻塞。
  6. 线程 2 被线程 1 预占。
  7. 线程 1 执行,由于在 //2 处实例仍旧为 null ,线程 1 还创建一个 Singleton 对象并将其引用赋值给 instance
  8. 线程 1 退出 synchronized 块并从 getInstance() 方法返回实例。
  9. 线程 1 被线程 2 预占。
  10. 线程 2 获取 //1 处的锁并检查 instance 是否为 null。
  11. 由于instance是非null的,并没有创建第二个Singleton对象,由线程1创建的对象被返回。

双重检查锁定背后的理论是完美的。不幸地是,现实完全不同。双重检查锁定的问题是:并不能保证它会在单处理器或多处理器计算机上顺利运行。因为Java平台内存模型,内存模型允许所谓的“无序写入”,这也是这些习语失败的一个主要原因。

当线程2进入getInstance() 方法,可能会直接得到一个尚未执行构造函数的对象。所以我们需要引入volatile,来保证顺序。所以最后的代码应该是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Singleton4 {

private Singleton4(){}

private static volatile Singleton4 single4 =null;

public static Singleton4 getInstance(){
if(single4==null){
synchronized (Singleton4.class){
if (single4 == null){
single4 = new Singleton4();
}
}
}
return single4;
}

}

但是我们可以知道,之前双重检查锁的原因是,我们看以下的伪代码

  1. memory=allocate(); //1:分配对象的内存空间
  2. ctorInstance(memory); //2:初始化对象
  3. instance=memory; //3:设置instance指向刚分配的内存地址
    上面3行代码中的2和3之间,可能会被重排序导致先3后2, 而volatile是要保证可见性,即instance实例化后马上对其他线程可见,并没有能解决掉这个双重锁的问题。
    那我们要怎么办呢?

静态嵌套类 懒汉模式

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton5 {

private static class LazyHolder {
private static final Singleton5 INSTANCE = new Singleton5();
}

private Singleton5 (){}

public static final Singleton5 getInstance() {
return LazyHolder.INSTANCE;
}
}

之前的那么多方法,都是为了能保证一个线程,去得到这个示例。我们换个方式利用了classloader的机制来保证初始化instance时只有一个线程,所以也是线程安全的,同时没有性能损耗.
写了这么多,也把单例模式真正的整理了七七八八,果然程序员还是需要多交流,多思考~还是很希望有许多老java,来指导指导我们这些刚刚步入java大门的菜鸟们~

登记式单例(查找资料的时候看到的,也一起分享了,可忽略)

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
//类似Spring里面的方法,将类名注册,下次从里面直接获取。
public class Singleton6 {
private static Map<String,Singleton6> map = new HashMap<String,Singleton6>();
static{
Singleton6 singleton6 = new Singleton6();
map.put(singleton6.getClass().getName(), singleton6);
}
//保护的默认构造子
protected Singleton6(){}
//静态工厂方法,返还此类惟一的实例
public static Singleton6 getInstance(String name) {
if(name == null) {
name = Singleton6.class.getName();
System.out.println("name == null"+"--->name="+name);
}
if(map.get(name) == null) {
try {
map.put(name, (Singleton6) Class.forName(name).newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return map.get(name);
}
//一个示意性的商业方法
public String about() {
return "Hello, I am RegSingleton.";
}
public static void main(String[] args) {
Singleton6 singleton6 = Singleton6.getInstance(null);
System.out.println(singleton6.about());
}
}

最后的最后,如果大家也是懒汉,对应的demo可以直接直接点单例模式


Github 不要吝啬你的star ^.^
更多精彩 戳我

Follow me on GitHub