单例模式是设计模式中创建型模式的一种
单例模式就是要确保类在内存中只有一个对象
该实例必须自动创建,并对外提供

优点与缺点

优点:
    在系统中只存在一个对象,因此可以节约系统资源
    对于一些需要频繁创建与销毁的对象,使用它可以提高性能
缺点:
    没有抽象层,因此难以扩展
    职责过重,在一定程度上违背了单一原则

案例

单例模式是面试的一个常考点
证明这个模式在开发中使用很频繁且很重要
    其核心思想 :
        构造私有化
        以静态方法或枚举返回实例对象
        保证只有一个实例(重点)
        确保反序列化不会重构对象

饿汉

在jvm类加载的过程中,有且仅有一个线程执行加载
在类加载的时候就创建对象,具体代码如下 :    (结果为true)
1
2
3
4
5
6
7
8
9
10
public class Self {
private static Self self = new Self();
private Self(){}
public static Self getSelf(){return self;}
}
public static void main(String[] args) {
Self self1 = Self.getSelf();
Self self2 = Self.getSelf();
System.out.println(self1 == self2);
}

懒汉

类加载的时候不创建对象,而等需要使用对象的时候才创建
存在同步问题,具体代码如下 : (结果为true)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class You {
private static You you = null;
private You(){}
public synchronized static You getYou(){
if (you == null)
you = new You();
return you;
}
}
public static void main(String[] args) {
You you1 = You.getYou();
You you2 = You.getYou();
System.out.println(you1 == you2);
}
但是上述懒汉模式也同时存在同步效率问题
为了兼顾延迟加载与性能上的需求,可以采用双检锁模式(Double Check Look)
具体改动如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
public class You {
private static You you = null;
public static You getYou() {
if (you == null) {
synchronized (You.class) {
if (you == null) {
you = new You();
}
}
}
return you;
}
}
但是由于jvm存在乱序执行功能
    1.在堆内存开辟内存空间
    2.在堆内存实例化SingleTon里面的各个参数
    3.把对象指向堆内存
可能在2还没执行就执行了3,若此时切换到另一个线程,由于对象有了指向
    就会被直接拿出来使用,就会出现异常.这就是著名的DCL失效问题
在JDK6以后只需要定义为以下格式即可 : (添加volatile关键字)
1
private volatile static You you = null;

静态内部工厂

相当于是对饿汉模式的改进,它在满足延迟加载的情况下同时满足线程安全
    因为加载外部类的时候并不会立即加载内部类
    至于多个类加载内部类文件和饿汉的原理一样
具体代码如下 :
1
2
3
4
5
6
7
8
9
public class You {
public static You getYou(){
return YouFactory.you;
}

private static class YouFactory{
private static You you = new You();
}
}

枚举

安全,效率高,不能实现延迟加载,可以天然防止反射与反序列化调用

同步问题的简单演示 :    (本来应该加到10的数据却只加到6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class You {
private static You you = null;
private int count = 0;
public synchronized int getCount(){return ++count;}
private You(){}
public static You getYou(){
if (you == null)
you = new You();
return you;
}
}
public static void main(String[] args) {
for (int i = 0;i < 10;i++)
new Thread(){
@Override
public void run() {
You you = You.getYou();
System.out.println(getName()+" "+you.getCount());
}
}.start();
}
执行结果不一定会出问题,但是一旦有同时通过判断的线程
就会出现线程安全问题,以下是出现问题的执行结果 :

JDK的单例模式

其实JDK也有类使用了单例模式,比如Runtime、

Runtime相关源码

可以看出JDK都使用的单例模式的饿汉式
故而开发一般都使用饿汉式
1
2
3
private static final Runtime currentRuntime = new Runtime();
private Runtime() {}
public static Runtime getRuntime() { return currentRuntime;}