常见的场景

(1)比如说,你自定义了一个框架,自定义了一份xml格式的一个配置文件,你要读取这个配置文件,这个配置文件中的数据,读取到类中,这个类的实例,只要保存一份就可以。那么此时可以使用单例模式,将这个类做成他的实例只能有一个,在这个实例中保存了配置文件中的数据 (2)类似于我们之前给大家讲解的那个工厂模式,有些工厂是需要实例化对象的,要基于实例化对象,来实现继承、接口、实现等功能,工厂实例可以做成单例的,就可以了 (3)你自己去判断,如果是一个类的实例只需要保持一份,那就做成单例

1.饿汉模式

  1. package com.example.demo.pattern.singleton;
  2. /**
  3. * 饿汉模式
  4. * @author chenchao
  5. *
  6. */
  7. public class HungrySingletonPatternDemo {
  8. public static void main(String[] args) {
  9. Singleton singleton = Singleton.getInstance();
  10. singleton.execute();
  11. new Singleton();
  12. }
  13. public static class Singleton {
  14. /**
  15. * 第一步:直接就是将这个类的实例在创建出来,赋予static final修饰的变量
  16. *
  17. * static:就是一个类的静态变量
  18. * final:这个变量的引用第一次初始化赋予之后,就再也不能修改引用了
  19. *
  20. */
  21. private static final Singleton instance = new Singleton();
  22. /**
  23. * 第二步:将构造函数搞成private私有的
  24. *
  25. * 此时除了这个类自己本身,其他任何人都不能创建它的这个实例对象
  26. *
  27. */
  28. private Singleton() {
  29. }
  30. /**
  31. * 第三步:给一个static静态方法,返回自己唯一的内部创建的一个实例
  32. * @return
  33. */
  34. public static Singleton getInstance() {
  35. return instance;
  36. }
  37. public void execute() {
  38. System.out.println("单例类的方法");
  39. }
  40. }
  41. }

2.常用内部内

  1. package com.example.demo.pattern.singleton;
  2. /**
  3. * 这个才是我们实际开发过程中,最最常用的单例模式,内部类的方式来实现
  4. *
  5. * @author chenchao
  6. *
  7. */
  8. public class InnerClassFullSingletonPatternDemo {
  9. /**
  10. * 可以做饱汉模式
  11. *
  12. * 内部类,只要没有被使用,就不会初始化,Singleton的实例就不会创建
  13. *
  14. * 在第一次有人调用getInstance方法的时候,内部类会初始化,创建一个Singleton的实例
  15. *
  16. * 然后java能确保的一点是,类静态初始化的过程一定只会执行一次
  17. *
  18. * @author chenchao
  19. *
  20. */
  21. public static class Singleton {
  22. private Singleton() {
  23. }
  24. public static class InnerHolder {
  25. public static final Singleton instance = new Singleton();
  26. }
  27. public static Singleton getInstance() {
  28. return InnerHolder.instance;
  29. }
  30. }
  31. }

3.饱汉模式 线程不安全

  1. package com.example.demo.pattern.singleton;
  2. /**
  3. * 线程不安全的饱汉模式
  4. * @author chenchao
  5. *
  6. */
  7. public class UnsafeFullSingletonPatternDemo {
  8. /**
  9. * 线程不安全
  10. *
  11. * @author chenchao
  12. *
  13. */
  14. public static class Singleton {
  15. private static Singleton instance;
  16. private Singleton() {
  17. }
  18. public static Singleton getInstance() {
  19. /*
  20. * 假设有两个线程过来
  21. *
  22. * 线程的基础:线程是并发着执行的,cpu,先执行一会儿线程1,然后停止执行线程1;切换过去执行线程2
  23. * 执行线程2一会儿之后,再停止执行线程2;回来继续执行线程1
  24. *
  25. * 第一个线程,判断发现说instance == null,代码就进入到了下面去
  26. * 第二个线程,执行到这儿,发现,此时instance == null,那么就没什么问题了,继续往下走
  27. *
  28. */
  29. if(instance == null) {
  30. // 第一个线程跑到了这儿来,但是此时第一个线程,还没有执行下面的那行代码
  31. // 此时,第二个线程代码也执行到了这儿,cpu切换回线程1
  32. // 执行线程1的代码,线程1会创建一个实例出来
  33. // 但是切换到线程2去执行的时候,线程2,的代码已经执行到这儿来了,此时又会再一次执行下面的代码
  34. // 就是会再一次创建一个实例,之前线程1创建的那个实例,就会被垃圾回收,废弃掉了
  35. instance = new Singleton();
  36. }
  37. return instance;
  38. }
  39. }
  40. }

4. 线程安全 double check Lock DCL

虽然上了DCL双重检查但是还是有一定几率线程不安全,推荐用第二种内部内方式,基于JDK线程安全。

  1. package com.example.demo.pattern.singleton;
  2. /**
  3. * 饿汉模式
  4. * @author chenchao
  5. *
  6. */
  7. public class SafeFullSingletonPatternDemo {
  8. public static class Singleton {
  9. private static Singleton instance;
  10. private Singleton() {
  11. }
  12. // 不是完美的
  13. // 因为不同的JVM的编译器的问题,可能导致说,这个情况下,还是线程不安全的
  14. // 具体的我不再这儿讲,因为涉及到复杂的JVM内部的原理
  15. public static Singleton getInstance() {
  16. // 如果线程1和线程2都执行到了这一步,然后此时线程1判断发现还是null
  17. // 线程2此时判断发现instance == null,也会进去
  18. if(instance == null) {
  19. // 线程1就会进来,此时线程1停止,切换到线程2
  20. // 线程2也会进来,此时切换到线程1
  21. // 线程1,发现这里需要加锁, 在这里加锁,获取到了这个锁
  22. // 线程2过来,线程2发现说,我也想要在这里加锁,发现说这个锁被人加了,线程2挂起等待别人释放锁
  23. // 此时切换回线程2,线程2发现锁被释放,然后在这里加锁
  24. synchronized(SafeFullSingletonPatternDemo.class) {
  25. // 线程1就进来了,此时切换到线程2
  26. // 切换回线程1,线程1此时在这里,再次判断,instance == null
  27. // 线程2就进来了,double check,如果这里没有instance == null的判断,那么线程2就会再次创建
  28. // 一个实例
  29. // 但是这里是双重检查,线程2又判断了一下,instance == null?否,不是null
  30. if(instance == null) {
  31. // 线程1就会进来,创建一个实例
  32. instance = new Singleton();
  33. }
  34. }
  35. }
  36. // 这边出来以后,线程1就释放锁了
  37. // 线程2跳出来,直接获取一个instance返回了,这个instance就是之前线程1创建的实例
  38. return instance;
  39. }
  40. }
  41. }

5.说明

最简单的一种类了,就是如果一个类就只需要一次,那么就使用这个类了

但是使用单例模式有一个要求,不允许这个类的逻辑过于复杂,一般就是持有某份配置文件的配置,或者是别的一些数据