友情支持

如果您觉得这个笔记对您有所帮助,看在D瓜哥码这么多字的辛苦上,请友情支持一下,D瓜哥感激不尽,😜

支付宝

微信

有些打赏的朋友希望可以加个好友,欢迎关注D 瓜哥的微信公众号,这样就可以通过公众号的回复直接给我发信息。

wx jikerizhi

公众号的微信号是: jikerizhi因为众所周知的原因,有时图片加载不出来。 如果图片加载不出来可以直接通过搜索微信号来查找我的公众号。

3. 单例模式

如果你不对构造方法做改动的话,是不可能阻止他人不去用new的。所以我们完全可以直接就把这个类的构造方法改成私有(private),你应该知道,所有类都有构造方法,不编码则系统默认生成空的构造方法,若有显示定义的构造方法,默认的构造方法就会失效。

客户端不再考虑是否需要去实例化的问题,而把责任都给了应该负责的类去处理。其实这就是一个很基本的设计模式:单例模式。

通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。[DP]

Singleton类,定义一个GetInstance操作,允许客户访问它的唯一实例。GetInstance是一个静态方法,主要负责创建自己的唯一实例。

单例模式除了可以保证唯一的实例

单例模式因为Singleton类封装它的唯一实例,这样它可以严格地控制客户怎样访问它以及何时访问它。简单地说就是对唯一实例的受控访问。

实用类通常也会采用私有化的构造方法来避免其有实例。

实用类不保存状态,仅提供一些静态方法或静态属性让你使用,而单例类是有状态的。实用类不能用于继承多态,而单例虽然实例唯一,却是可以有子类来继承。实用类只不过是一些方法属性的集合,而单例却是有着唯一的对象实例。

lock是确保当一个线程位于代码的临界区时,另一个线程不进入临界区。如果其他线程试图进入锁定的代码,则它将一直等待(即被阻止),直到该对象被释放。[MSDN]

这段代码使得对象实例由最先进入的那个线程创建,以后的线程在进入时不会再去创建对象实例了。由于有了lock,就保证了多线程环境下的同时访问也不会造成多个实例的生成。

不用让线程每次都加锁,而只是在实例未被创建的时候再加锁处理。同时也能保证多线程的安全。这种做法被称为Double-Check Locking(双重锁定)。

C#与公共语言运行库也提供了一种‘静态初始化’方法,这种方法不需要开发人员显式地编写线程安全代码,即可解决多线程环境下它是不安全的问题。[MSDN]

这样的实现与前面的示例类似,也是解决了单例模式试图解决的两个基本问题:全局访问和实例化控制,公共静态属性为访问实例提供了一个全局访问点。不同之处在于它依赖公共语言运行库来初始化变量。由于构造方法是私有的,因此不能在类本身以外实例化Singleton类;因此,变量引用的是可以在系统中存在的唯一的实例。不过要注意,instance变量标记为readonly,这意味着只能在静态初始化期间或在类构造函数中分配变量[MSDN]。由于这种静态初始化的方式是在自己被加载时就将自己实例化,所以被形象地称之为饿汉式单例类,原先的单例模式处理方式是要在第一次被引用时,才会将自己实例化,所以就被称为懒汉式单例类。[J&DP]

饿汉式,即静态初始化的方式,它是类一加载就实例化的对象,所以要提前占用系统资源。然而懒汉式,又会面临着多线程访问的安全性问题,需要做双重锁定这样的处理才可以保证安全。所以到底使用哪一种方式,取决于实际的需求。

3.1. 使用反射和反序列化产生多个“单例类”实例

D瓜哥根据以前学习设计模式的经验来看,单例模式的实现类只能产生一个对象。估计这也是我们大家普遍看法。但是,在一次面试时,被面试官问到“单例模式的实现类能否产生多个对象?”然后,D瓜哥当场就Hold不住了。

不过,当时善意的面试官提醒,可以用反射。昨天看《Java 程序性能优化》时,提到用反序列化也可以产生多个方法。然后,又被我同学问到这个问题。干脆写一篇文章,总结一下吧。

3.2. 定义

根据 GoF 的著名著作 《设计模式》,单例模式的定义如下:

单例模式(Singleton)

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

— Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides
《设计模式》
Diagram

在Java中,单例模式实现方式有两种:

  • 静态初始化的方式是在自己被加载时就将自己实例化。这种方式被称为“饿汉式单例类”;

  • 在第一次被引用时,才会将自己实例化。这种方式被称为“懒汉式单例类”。

下面做针对介绍。

3.3. 饿汉式单例类

饿汉式单例类的实现代码如下:

代码 1. 饿汉式单例类
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
package com.diguage.didp.singleton;

/**
 * 饿汉式单例类
 *
 * @author D瓜哥, https://www.diguage.com/
 * @since 2014-5-26.
 */
public class HungrySingleton {
  private static HungrySingleton instance = new HungrySingleton();

  private HungrySingleton() {}

  public static HungrySingleton getInstance() {
    return instance;
  }
}

3.4. 懒汉式单例类

懒汉式单例类的实现代码如下:

代码 2. 懒汉式单例类
 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
package com.diguage.didp.singleton;

/**
 * 懒汉式单例类
 *
 * <p>Coder:D瓜哥, https://www.diguage.com/
 *
 * <p>Date:2014-5-26.
 */
public class LazySingleton {
  private static LazySingleton instance = null;

  private LazySingleton() {}

  public static LazySingleton getInstance() {
    if (null == instance) {
      synchronized (LazySingleton.class) {
        if (null == instance) {
          instance = new LazySingleton();
        }
      }
    }
    return instance;
  }
}
代码 3. 懒汉式单例类的并发测试
 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
39
40
41
42
43
44
45
46
47
48
49
50
package com.diguage.didp.singleton;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 单例模式并发性测试
 *
 * @author D瓜哥, https://www.diguage.com/
 * @since 16/11/2016.
 */
public class ConcurrentTest {
  public static void main(String[] args) {
    int THREAD_COUNT = 10000;
    CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
    ConcurrentMap concurrentMap = new ConcurrentHashMap();
    ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);
    for (int i = 0; i < THREAD_COUNT; i++) {
      executorService.execute(new SingletonFactory(latch, concurrentMap, i));
      latch.countDown();
    }
    executorService.shutdown();
  }
}

class SingletonFactory implements Runnable {
  private CountDownLatch latch;
  private ConcurrentMap concurrentMap;
  private int id;

  public SingletonFactory(CountDownLatch latch, ConcurrentMap concurrentMap, int id) {
    this.latch = latch;
    this.concurrentMap = concurrentMap;
    this.id = id;
  }

  public void run() {
    try {
      latch.await();
      LazySingleton instance = LazySingleton.getInstance();
      concurrentMap.put(instance, instance);
      System.out.println(id + "\t" + concurrentMap.size());
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}
代码 4. 带 volatile 修饰的懒汉式单例类
 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
package com.diguage.didp.singleton;

import java.io.Serializable;

/**
 * 带 <code>volatile</code> 修饰的懒汉式单例类
 *
 * @author D瓜哥, https://www.diguage.com/
 * @since 2014-5-26.
 */
public class VolatileLazySingleton implements Serializable {
  private static volatile VolatileLazySingleton instance = null;

  private VolatileLazySingleton() {}

  public static VolatileLazySingleton getInstance() {
    if (null == instance) {
      synchronized (VolatileLazySingleton.class) {
        if (null == instance) {
          instance = new VolatileLazySingleton();
        }
      }
    }
    return instance;
  }
}
代码 5. 可序列化懒汉式单例类
 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
package com.diguage.didp.singleton;

import java.io.Serializable;

/**
 * 可以序列化的懒汉式单例类
 *
 * <p>注:不正确
 *
 * @author D瓜哥, https://www.diguage.com/
 * @since 2014-5-26.
 */
public class SerializableLazySingleton implements Serializable {
  private static volatile SerializableLazySingleton instance = null;

  private SerializableLazySingleton() {}

  public static SerializableLazySingleton getInstance() {
    if (null == instance) {
      synchronized (SerializableLazySingleton.class) {
        if (null == instance) {
          instance = new SerializableLazySingleton();
        }
      }
    }
    return instance;
  }
}
代码 6. 正确的可序列化懒汉式单例类
 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
package com.diguage.didp.singleton;

import java.io.Serializable;

/**
 * 可以序列化的懒汉式单例类
 *
 * @author D瓜哥, https://www.diguage.com/
 * @since 2014-5-26.
 */
public class CorrectSerializableLazySingleton implements Serializable {

  private static volatile CorrectSerializableLazySingleton instance = null;

  private CorrectSerializableLazySingleton() {}

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

  private Object readResolve() {
    return instance;
  }
}
代码 7. 序列化测试
 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
39
40
41
package com.diguage.didp.singleton;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

/**
 * 序列化测试
 *
 * @author D瓜哥, https://www.diguage.com/
 * @since 16/11/2016.
 */
public class SerializationTest {
  public static void main(String[] args) throws IOException, ClassNotFoundException {

    testSerialization(SerializableLazySingleton.getInstance());
    testSerialization(CorrectSerializableLazySingleton.getInstance());
    testSerialization(Singleton.INSTANCE);
  }

  public static <T> void testSerialization(T t) throws IOException, ClassNotFoundException {
    // 将对象写入数组中
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    oos.writeObject(t);
    baos.close();

    byte[] objectByteArray = baos.toByteArray();

    // 从数组中读取对象
    ByteArrayInputStream bais = new ByteArrayInputStream(objectByteArray);
    ObjectInputStream ois = new ObjectInputStream(bais);
    T newInstance = (T) ois.readObject();
    //判断是否是同一个对象
    Class<?> clazz = t.getClass();
    String canonicalName = clazz.getCanonicalName();
    System.out.println(canonicalName + ": " + (newInstance == t));
  }
}
代码 8. 枚举实现的单例模式
1
2
3
4
5
6
7
8
9
package com.diguage.didp.singleton;

/**
 * @author D瓜哥, https://www.diguage.com/
 * @since 16/11/2016.
 */
public enum Singleton {
  INSTANCE
}
代码 9. 单例模式的反射测试
 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
package com.diguage.didp.singleton;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * 反射测试
 *
 * @author D瓜哥, https://www.diguage.com/
 * @since 16/11/2016.
 */
public class ReflectionTest {
  public static void main(String[] args)
      throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,
          InstantiationException {
    testReflection(CorrectSerializableLazySingleton.getInstance());
    //    testReflection(Singleton.INSTANCE);
  }

  private static <T> void testReflection(T t)
      throws NoSuchMethodException, InstantiationException, IllegalAccessException,
          InvocationTargetException {
    Class<?> clazz = t.getClass();
    Class<?>[] params = {};
    Constructor<?> constructor = clazz.getDeclaredConstructor(params);
    constructor.setAccessible(true);
    T instance = (T) constructor.newInstance();
    System.out.println((instance == t) + "\t:\t" + clazz.getCanonicalName());
  }
}
代码 10. 单例模式的 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
package com.diguage.didp.singleton;

import sun.misc.Unsafe;

import java.lang.reflect.Field;

/**
 * {@link Unsafe} 创建实例测试
 *
 * @author D瓜哥, https://www.diguage.com/
 * @since 16/11/2016.
 */
public class UnsafeTest {
  public static void main(String[] args)
      throws NoSuchFieldException, IllegalAccessException, InstantiationException {
    testUnsafe(CorrectSerializableLazySingleton.getInstance());
    testUnsafe(Singleton.INSTANCE);
  }

  private static <T> void testUnsafe(T t)
      throws NoSuchFieldException, IllegalAccessException, InstantiationException {
    Unsafe unsafe = getUnsafe();
    Class<?> clazz = t.getClass();
    T instance = (T) unsafe.allocateInstance(clazz);
    System.out.println((instance == t) + "\t:\t" + clazz.getCanonicalName());
  }

  private static Unsafe getUnsafe() throws NoSuchFieldException, IllegalAccessException {
    // 通过反射得到theUnsafe对应的Field对象
    Field field = Unsafe.class.getDeclaredField("theUnsafe");
    // 设置该Field为可访问
    field.setAccessible(true);
    // 通过Field得到该Field对应的具体对象,传入null是因为该Field为static的
    return (Unsafe) field.get(null);
  }
}

两种方式各有优缺。饿汉式,即静态初始化的方式,它是类一加载就实例化对象。所以,加载时间比较长,而且要提前占用系统资源,如果后续用不到这个对象,会造成浪费。懒汉式,又会面临多线程访问的安全性问题,需要使用双重锁定才能保证安全性,由于 Java 虚拟机的问题,在 JDK5 以后双重锁定能可以正常工作;另外,由于是在使用时初始化,在第一次调用时耗时比较长。所以,至于到底使用哪种方式,取决于实际需求。

代码 11. 并发性测试
 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
39
40
41
42
43
44
45
46
47
48
49
50
package com.diguage.didp.singleton;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 单例模式并发性测试
 *
 * @author D瓜哥, https://www.diguage.com/
 * @since 16/11/2016.
 */
public class ConcurrentTest {
  public static void main(String[] args) {
    int THREAD_COUNT = 10000;
    CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
    ConcurrentMap concurrentMap = new ConcurrentHashMap();
    ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);
    for (int i = 0; i < THREAD_COUNT; i++) {
      executorService.execute(new SingletonFactory(latch, concurrentMap, i));
      latch.countDown();
    }
    executorService.shutdown();
  }
}

class SingletonFactory implements Runnable {
  private CountDownLatch latch;
  private ConcurrentMap concurrentMap;
  private int id;

  public SingletonFactory(CountDownLatch latch, ConcurrentMap concurrentMap, int id) {
    this.latch = latch;
    this.concurrentMap = concurrentMap;
    this.id = id;
  }

  public void run() {
    try {
      latch.await();
      LazySingleton instance = LazySingleton.getInstance();
      concurrentMap.put(instance, instance);
      System.out.println(id + "\t" + concurrentMap.size());
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

3.5. 产生多个“单例类”实例

还回到文章开头,D瓜哥被问到的问题:单例模式的实现类能否产生多个对象?既然写这篇文章,那么答案肯定是肯定的。那么怎么产生呢?

有两种方式可以做到。我们这里分别来试验一下。

问题:既然 static 变量是类共享的,修改后,其他对象也应该立即修改,那么还需要 volidate 吗?

3.6. 枚举

《The Java® Language Specification Java SE 17 Edition》

An enum type has no instances other than those defined by its enum constants. It is a compile-time error to attempt to explicitly instantiate an enum type (§15.9.1).

In addition to the compile-time error, three further mechanisms ensure that no instances of an enum type exist beyond those defined by its enum constants:

  • The final clone method in Enum ensures that enum constants can never be cloned.

  • Reflective instantiation of enum types is prohibited.

  • Special treatment by the serialization mechanism ensures that duplicate instances are never created as a result of deserialization.

— James Gosling, Bill Joy, Guy Steele, Gilad Bracha, Alex Buckley
《The Java® Language Specification Java SE 17 Edition》 -- 8.9. Enum Types

3.7. 注册表或者应用上下文

后续补充内容,典型例子就是 Spring 框架。