Qicz’s Thoughts HUB

The creative and technical writing. Do more, challenge more, know more, be more.

软件设计模式之单例模式

单例模式是软件设计中非常常见的模式,但真正用好也用对的好像还有很多路要走。一起来研究一下。

单例,最最起码得有这些吧

  • 私有的构造方案
  • 一个 static 的实例
  • 一个 static 的外部访问入口

具体代码应该是这样的(懒汉模式)

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

当然了,看着好像是那么回事,完成了以上操作,我们来测试一下

 1public class SingletonTest {
 2
 3    public static void main(String[] args) {
 4        for (int i = 0; i < 3; i++) {
 5            //test1
 6            Singleton singleton = Singleton.getInstance();
 7            System.out.println("running at thread" + Thread.currentThread().getName() + ">>" + singleton);
 8        }
 9    }
10}

输出(每次运行输出是不一样的)

1running at threadmain>>cn.zhucongqi.singleton.Singleton@49476842
2running at threadmain>>cn.zhucongqi.singleton.Singleton@49476842
3running at threadmain>>cn.zhucongqi.singleton.Singleton@49476842

单例完成了!?

我们在多线程下测试一下,改造一下

 1public class SingletonTest {
 2
 3    public static void main(String[] args) {
 4//        for (int i = 0; i < 3; i++) {
 5//            //test1
 6//            Singleton singleton = Singleton.getInstance();
 7//            System.out.println("running at thread" + Thread.currentThread().getName() + ">>" + singleton);
 8//        }
 9
10
11        //test2
12        Runnable r = new Runnable() {
13            @Override
14            public void run() {
15                Singleton singleton = Singleton.getInstance();
16                System.out.println("running at thread" + Thread.currentThread().getName() + ">>" + singleton);
17            }
18        };
19        new Thread(r).start();
20        new Thread(r).start();
21        new Thread(r).start();
22        new Thread(r).start();
23        new Thread(r).start();
24    }
25}

输出(每次运行输出是不一样的)

1running at threadThread-2>>cn.zhucongqi.singleton.Singleton@a1a1c96
2running at threadThread-5>>cn.zhucongqi.singleton.Singleton@b0545bc
3running at threadThread-4>>cn.zhucongqi.singleton.Singleton@a1a1c96
4running at threadThread-3>>cn.zhucongqi.singleton.Singleton@a1a1c96
5running at threadThread-0>>cn.zhucongqi.singleton.Singleton@b0545bc
6running at threadThread-1>>cn.zhucongqi.singleton.Singleton@b0545bc

出现问题了!!!怎么在多线程下出现了不同实例了???是不是我们的设计有问题呢?我们在图示位置打个断点,按照 Thread 来处理

然后 Debug 一下

我们先来看看 Thread0 的情况

看到此时instance为{Singleton@475}。我们换到 Thread1 再看看 看到此时instance也是{Singleton@475}。往下走

额等等,怎么instance变成{Singleton@476}了,被覆盖咯??? 看来我们的设计有问题哦!

怎么改造呢?

从 Debug 来看,是getInstance()时出现了线程安全问题,我们用synchronized是不是可以解决问题呢?我们再改造一下

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

再 Debug 一下。 看到就一个 Thread 是 running 的,其他的是 Monitor 的,是不是可以了。我们往下继续走。 看到,Thread0 走出来之后,Thread4 才进入到 Running 状态。也就是加入synchronized把整个class都锁住了。目的达到,但是性能不好。我们再改造一下

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

再Debug 看看 现在所有 Thread 都是 Running 的了。我们继续走,先看看 Thread0 再看看 Thread3 看样子又要被覆盖咯!!!看来还是不行哦!是不是我们再加一把锁哦?!

我们再改造一下

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

这次我们先 Run 一下看看结果,输出

1running at threadThread-2>>cn.zhucongqi.singleton.Singleton@b0545bc
2running at threadThread-0>>cn.zhucongqi.singleton.Singleton@b0545bc
3running at threadThread-1>>cn.zhucongqi.singleton.Singleton@b0545bc
4running at threadThread-3>>cn.zhucongqi.singleton.Singleton@b0545bc
5running at threadThread-4>>cn.zhucongqi.singleton.Singleton@b0545bc

结果对咯!为什么呢?

其实,刚才的效果和不断的改造,我们把最初的Singleton改造成了DoubleCheck 的懒汉式Singleton了。

当然了,上面这种方式只是一种方式罢了。下面直接上其他的方式Code。

2020.2.20更新

在Doublecheck的第一次check的时候,代码读取到instance不为null时,instance引用的对象有可能还没有完成初始化。主要原因是重排序,**所以只需要做一点小的修改(把instance声明为volatile型),就可以实现线程安全的延迟初始化。**因为被volatile关键字修饰的变量是被禁止重排序的。

1private static volatile Singleton instance = null;

EnumSingleton 枚举方式实现的单例

1// Enum Singleton
2public enum EnumSingleton {
3
4    INSTANCE;
5    
6    public static EnumSingleton getInstance() {
7        return INSTANCE;
8    }
9}

InnerClassSingleton 内部类方式实现的单例

 1// Inner class Singleton
 2public class InnerSingleton {
 3
 4    private InnerSingleton(){
 5        if (null != InnerHolder.INSTANCE) {
 6            throw new RuntimeException("Just only one Instance can be create!");
 7        }
 8    }
 9
10    //Serializable
11    private Object readResolve() {
12        return InnerHolder.INSTANCE;
13    }
14
15    public static InnerSingleton getInstance() {
16        return InnerHolder.INSTANCE;
17    }
18
19    private static class InnerHolder {
20        private static final InnerSingleton INSTANCE = new InnerSingleton();
21    }
22}

特别说明

  • 为了避免序列化出现的 Singleton 实例不唯一的情况,覆盖 Object 的readResolve方法。
1private Object readResolve() {
2        return InnerHolder.INSTANCE;
3    }
  • 为了避免,通过反射错误的使用 Singleton,在构造中做再处理
1 private InnerSingleton(){
2        if (null != InnerHolder.INSTANCE) {
3            throw new RuntimeException("Just only one Instance can be create!");
4        }
5}