Singleton
单例Singleton是最常见的设计模式,如何实现单例呢?如果实现线程安全的单例呢?
单线程单例
最常见的单例写法
- 声明一个静态实例
- 构造函数声明为私有,确保不能通过构造函数创建对象
- getInstance()时进行null判断
public class OneThreadSingleton { private static OneThreadSingleton instance = null; private OneThreadSingleton() { } public static OneThreadSingleton getInstance() { if (null == instance) { instance = new OneThreadSingleton(); } return instance; } }
|
以上代码在单线程环境下进行测试,测试代码如下
public class SingletonExample { public static void main(String[] args) { for (int i=0; i<10; i++) { OneThreadSingleton singleton = OneThreadSingleton.getInstance(); System.out.println(singleton); } } }
|
返回结果如下,都是一个实例,说明单例成功
cn.waterlu.java.design.OneThreadSingleton@6f75e721 cn.waterlu.java.design.OneThreadSingleton@6f75e721 cn.waterlu.java.design.OneThreadSingleton@6f75e721 cn.waterlu.java.design.OneThreadSingleton@6f75e721 cn.waterlu.java.design.OneThreadSingleton@6f75e721 cn.waterlu.java.design.OneThreadSingleton@6f75e721 cn.waterlu.java.design.OneThreadSingleton@6f75e721 cn.waterlu.java.design.OneThreadSingleton@6f75e721 cn.waterlu.java.design.OneThreadSingleton@6f75e721 cn.waterlu.java.design.OneThreadSingleton@6f75e721
|
但是以上代码在多线程环境下是有问题的,多线程测试代码如下
public class SingletonExample { public static void main(String[] args) { List<Task> taskList = new ArrayList<>(); for (int i=0; i<10; i++) { Task task = new Task(); taskList.add(task); } for (Task task : taskList) { task.start(); } } public static class Task extends Thread { @Override public void run() { OneThreadSingleton singleton = OneThreadSingleton.getInstance(); System.out.println(singleton); } } }
|
返回结果如下,存在不一样的情况,单例失败
- 注意,结果是随机的,需要多运行几次才能出现返回不一样实例的情况
- 错误原因在于并发进行(null==instance)判断是不准确的,可能存在多个线程判断时instance都是null的情况
cn.waterlu.java.design.OneThreadSingleton@65d2066b cn.waterlu.java.design.OneThreadSingleton@dbefca cn.waterlu.java.design.OneThreadSingleton@6464d21 cn.waterlu.java.design.OneThreadSingleton@65d2066b cn.waterlu.java.design.OneThreadSingleton@65d2066b cn.waterlu.java.design.OneThreadSingleton@65d2066b cn.waterlu.java.design.OneThreadSingleton@65d2066b cn.waterlu.java.design.OneThreadSingleton@65d2066b cn.waterlu.java.design.OneThreadSingleton@65d2066b cn.waterlu.java.design.OneThreadSingleton@65d2066b
|
同步单例
以上经典的单例代码在多线程情况是有问题的,也就是非线程安全的,如果如何写出线程安全的单例呢?
以下代码是最容易想到的,给getInstance()方法增加synchronized关键字,这样就能保证线程安全了
- 首先肯定这种写法是对的,没有问题,可以实现线程安全的单例模式
- 这种写法虽然简单,但存在效率问题:每一次进入synchronized代码块都是需要加锁的,加锁和释放锁肯定是有开销的;绝大多数情况下,instance不等于null,加锁只在instance等于null时起作用,典型的悲观锁
- 既然这样,把synchronized放到if()判断里面不就行了,这就引出了另外一种实现
public class SynchronizedSingleton { private static SynchronizedSingleton instance = null; private SynchronizedSingleton() { } public synchronized static SynchronizedSingleton getInstance() { if (null == instance) { instance = new SynchronizedSingleton(); } return instance; } }
|
双重检查单例
继续上面的思路,代码如下:
- synchronized代码块只在instance等于null时起作用,大多数情况下直接return instance即可
- 注意,这里有两个if (null == instance)判断,所以也被称为双重检查,那么为什么要做第二遍检查呢
- 因为synchronized虽然起到了互斥的作用,但是代码还是会执行的;假设,两个线程同时通过第一层null判断,抢到锁的线程执行了new操作;第一个线程释放锁以后第二个线程从阻塞状态唤醒执行,此时如果没有第二次判断,那么它还是会创建一个新的对象。
public class DoubleCheckSingleton { private static DoubleCheckSingleton instance = null; private DoubleCheckSingleton() { } public static DoubleCheckSingleton getInstance() { if (null == instance) { synchronized (DoubleCheckSingleton.class) { if (null == instance) { instance = new DoubleCheckSingleton(); } } } return instance; } }
|
静态单例
其实还有更简单的单例实现方法,代码如下
- 代码非常简单,也是线程安全的,因为多线程只发生了读操作,当然是安全的
- 当然,这么做也有缺点,那就是没有延迟加载,俗称lazy load
- 即使StaticSingleton类没有被使用,它也创建对象并占用了内存空间;上面的例子都是延迟加载的,在第一次被使用时创建的对象
备注:我真心觉得这么写就挺好,又简单又高效,耗费那点内存空间真不算什么。
public class StaticSingleton { private final static StaticSingleton instance = new StaticSingleton(); private StaticSingleton() { } public static StaticSingleton getInstance() { return instance; } }
|
内部静态类单例
- 在前面代码的基础上进行改造,把创建对象操作放到内部类中实现,以完成延迟加载
- 头一次看到这种代码可能会感到奇怪,一步一步分析过来就了解了
public class Singleton { private Singleton() { } private static class SingletonInner { private static Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonInner.instance; } }
|
这么多单例的实现方式,个人觉得这就和茴香豆的茴字有几种写法差不多,没啥意义。关键理解其中的思考方法,还有些意义。