在某个类或者整个系统中只能有一个实例对象可被获取和使用的代码模式

一、要素

  • 只能有一个实例
    • 构造器私有化
  • 必须自己创建自己这个实例
    • 还有一个该类的静态变量来保存这个唯一的实例对象
  • 必须自行向整个系统提供这个实例
    • 对外提供获取该实例对象的方式(直接暴露、提供get方法)

      二、几种常见形式

      • 饿汉式:直接创建对象,不存在线程安全问题
        • 直接实例化饿汉式(简洁直观)
        • 枚举式(最简洁)
        • 静态代码块饿汉式(适合复杂实例化)
      • 懒汉式:延迟创建对象
        • 线程不安全(适用于单线程)
        • 线程安全(适用于多线程)
        • 静态内部类形式(适用于多线程)

1、饿汉式

直接实例化饿汉式

  1. /**
  2. * @description:
  3. * 饿汉式:直接创建,不管你需不需要
  4. * @Author: wangchao
  5. * @Date: 2021/7/11
  6. */
  7. public class Singleton1 {
  8. public static final Singleton1 SINGLETON = new Singleton1();
  9. private Singleton1() {
  10. }
  11. }

测试

  1. public class Test1 {
  2. public static void main(String[] args) {
  3. Singleton1 singleton1 = Singleton1.SINGLETON;
  4. System.out.println(singleton1);
  5. }
  6. }

枚举式(最简洁)

  1. /**
  2. * @description:
  3. * 枚举类型:表示该类型的对象是有限的几个,我们可以限定为一个,就成了单例
  4. * @Author: wangchao
  5. * @Date: 2021/7/11
  6. */
  7. public enum Singleton2 {
  8. SINGLETON
  9. }

测试

  1. public class Test2 {
  2. public static void main(String[] args) {
  3. Singleton2 singleton2 = Singleton2.SINGLETON;
  4. System.out.println(singleton2);
  5. }
  6. }

静态代码块饿汉式(适合复杂实例化)
  1. public class Singleton3 {
  2. public static final Singleton3 SINGLETON;
  3. private String info;
  4. static {
  5. try {
  6. Properties properties = new Properties();
  7. properties.load(Singleton3.class.getClassLoader().getResourceAsStream("singleton.properties"));
  8. SINGLETON = new Singleton3(properties.getProperty("info"));
  9. } catch (IOException e) {
  10. throw new RuntimeException(e);
  11. }
  12. }
  13. private Singleton3(String info) {
  14. this.info = info;
  15. }
  16. public String getInfo() {
  17. return info;
  18. }
  19. }

测试

  1. public class Test3 {
  2. public static void main(String[] args) {
  3. Singleton3 singleton3 = Singleton3.SINGLETON;
  4. System.out.println(singleton3.getInfo());
  5. }
  6. }

2、懒汉式

线程不安全(适用于单线程)

  1. public class Singleton4 {
  2. private static Singleton4 singleton;
  3. private Singleton4() {
  4. }
  5. public static Singleton4 getSingleton() {
  6. if (singleton == null) {
  7. singleton = new Singleton4();
  8. }
  9. return singleton;
  10. }
  11. }

测试01

  1. // 简单测试
  2. public class Test4 {
  3. public static void main(String[] args) {
  4. Singleton4 singleton = Singleton4.getSingleton();
  5. Singleton4 singleton2 = Singleton4.getSingleton();
  6. System.out.println(singleton);
  7. System.out.println(singleton2);
  8. }
  9. }
  10. 结果 是同一个对象
  11. com.supkingx.base.b_singleton.Singleton4@5cad8086
  12. com.supkingx.base.b_singleton.Singleton4@5cad8086

测试02

  1. // 展示线程不安全
  2. public class Singleton4 {
  3. private static Singleton4 singleton;
  4. private Singleton4() {
  5. }
  6. public static Singleton4 getSingleton() {
  7. if (singleton == null) {
  8. try {
  9. // 为了展示线程不安全,这是睡眠100ms
  10. Thread.sleep(100);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. singleton = new Singleton4();
  15. }
  16. return singleton;
  17. }
  18. }
  19. public class Test4_1 {
  20. public static void main(String[] args) throws ExecutionException, InterruptedException {
  21. Callable<Singleton4> callable = new Callable<Singleton4>() {
  22. @Override
  23. public Singleton4 call() throws Exception {
  24. return Singleton4.getSingleton();
  25. }
  26. };
  27. ExecutorService executorService = Executors.newFixedThreadPool(2);
  28. Future<Singleton4> submit1 = executorService.submit(callable);
  29. Future<Singleton4> submit2 = executorService.submit(callable);
  30. System.out.println(submit1.get());
  31. System.out.println(submit2.get());
  32. }
  33. }
  34. 结果,不是同一个对象了
  35. com.supkingx.base.b_singleton.Singleton4@5e2de80c
  36. com.supkingx.base.b_singleton.Singleton4@1d44bcfa

线程安全(适用于多线程)

ps:这种写法也存在一定的风险:具体可以参考: https://www.yuque.com/wangchao-volk4/fdw9ek/zqskg7

  1. public class Singleton5 {
  2. private static Singleton5 singleton;
  3. private Singleton5() {
  4. }
  5. // DCL (double check lock双端检索机制),但是这种写法也不是最完美的写法,存在风险(指令重排),详细可见包 com.supkingx.base.f_thread.VolatileDemo,或者第十七章中关于volatile的介绍
  6. public static Singleton5 getSingleton() {
  7. // 外层的这个判断完全是为了性能考虑
  8. if (singleton == null) {
  9. // 与Singleton4线程不安全锁区别,加了synchronized后线程安全
  10. synchronized (Singleton5.class) {
  11. if (singleton == null) {
  12. try {
  13. Thread.sleep(1000);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. singleton = new Singleton5();
  18. }
  19. }
  20. }
  21. return singleton;
  22. }
  23. }
  24. // 测试
  25. public class Test5 {
  26. public static void main(String[] args) throws ExecutionException, InterruptedException {
  27. Callable<Singleton5> callable = new Callable<Singleton5>() {
  28. @Override
  29. public Singleton5 call() throws Exception {
  30. return Singleton5.getSingleton();
  31. }
  32. };
  33. ExecutorService executorService = Executors.newFixedThreadPool(2);
  34. Future<Singleton5> submit1 = executorService.submit(callable);
  35. Future<Singleton5> submit2 = executorService.submit(callable);
  36. System.out.println(submit1.get());
  37. System.out.println(submit2.get());
  38. }
  39. }
  40. 结果
  41. com.supkingx.base.b_singleton.Singleton5@5e2de80c
  42. com.supkingx.base.b_singleton.Singleton5@5e2de80c

静态内部类形式(适用于多线程)

  1. /**
  2. * @description: 懒汉式
  3. * 在内部类被加载和初始化时,才创建SINGLETON实例对象
  4. * 静态内部类不会自动随着外部类的加载和初始化而初始化,它是要单独加载和初始化的。
  5. * 因为是在内部类加载和初始化时创建的,所以是线程安全的。
  6. * @Author: wangchao
  7. * @Date: 2021/7/11
  8. */
  9. public class Singleton6 {
  10. private Singleton6() {
  11. }
  12. private static class Inner {
  13. private static final Singleton6 SINGLETON = new Singleton6();
  14. }
  15. private static Singleton6 getInstance() {
  16. return Inner.SINGLETON;
  17. }
  18. }
  19. // 测试
  20. public class Test6 {
  21. public static void main(String[] args) {
  22. Singleton6 singleton = Singleton6.getInstance();
  23. System.out.println(singleton);
  24. }
  25. }

3、对以上所有写法的优化

如上所述会出现指令重排的风险,所以需要加上volatile
1-Volatile

  1. private static volatile SingletonDemo3 singletonDemo = null;

至此,volatile避免指令重排,synchronized保证了原子性,完美。

三、总结

  • 如果是饿汉式,枚举形式最简单
  • 如果是懒汉式,静态内部类形式最简单