【设计模式】单例模式

学而不思则罔,思而不学则殆


简介

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

特点

  1. 单例类只有一个实例
  2. 单例类必须自己创建自己(所以构造函数私有)
  3. 单例类跟其他方提供这个实例

单例实现方式

懒汉式 - 线程不安全

public class LHSingleton {
    private static LHSingleton singleton; //懒汉式

    //私有构造方法
    private LHSingleton() {
        System.out.println("LHSingleton init:" + Thread.currentThread());
    }

    public static LHSingleton getSingleton() {
        if (singleton == null) {
            singleton = new LHSingleton();
        }
        return singleton;
    }
}

懒汉式,顾名思义就是实例在用到的时候才去创建,“比较懒”,用的时候才去检查有没有实例,如果有则返回,没有则新建。有线程安全和线程不安全两种写法,区别就是synchronized关键字。

测试一下不加synchronized关键字:

    //测试懒汉模式的安全性
    private static void testLH() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            executorService.submit(() -> {
                LHSingleton singleton = LHSingleton.getSingleton();
                System.out.println(singleton + " " + Thread.currentThread());
            });
        }
    }

结果如下(存在这种情况):

LHSingleton init:Thread[pool-2-thread-2,5,main]
LHSingleton init:Thread[pool-2-thread-1,5,main]
LHSingleton init:Thread[pool-2-thread-3,5,main]
com.pattern.singletonpattern.LHSingleton@7d082b84 Thread[pool-2-thread-1,5,main]
com.pattern.singletonpattern.LHSingleton@6b851772 Thread[pool-2-thread-2,5,main]
com.pattern.singletonpattern.LHSingleton@13f54313 Thread[pool-2-thread-3,5,main]
com.pattern.singletonpattern.LHSingleton@13f54313 Thread[pool-2-thread-5,5,main]
com.pattern.singletonpattern.LHSingleton@13f54313 Thread[pool-2-thread-4,5,main]
com.pattern.singletonpattern.LHSingleton@13f54313 Thread[pool-2-thread-6,5,main]
com.pattern.singletonpattern.LHSingleton@13f54313 Thread[pool-2-thread-7,5,main]
com.pattern.singletonpattern.LHSingleton@13f54313 Thread[pool-2-thread-9,5,main]
com.pattern.singletonpattern.LHSingleton@13f54313 Thread[pool-2-thread-10,5,main]
com.pattern.singletonpattern.LHSingleton@13f54313 Thread[pool-2-thread-8,5,main]

实际上新建了三个对象是实例。不属于对象唯一。

懒汉式 - 线程安全

public class LHSingleton {
    private static LHSingleton singleton; //懒汉式

    //私有构造方法
    private LHSingleton() {
        System.out.println("LHSingleton init:" + Thread.currentThread());
    }

    public static synchronized LHSingleton getSingleton() {
        if (singleton == null) {
            singleton = new LHSingleton();
        }
        return singleton;
    }
}

饿汉式 - 线程安全

public class EHSingleton {
    private static EHSingleton singleton = new EHSingleton(); //饿汉式

    //私有构造方法
    private EHSingleton() {
        System.out.println("EHSingleton init:" + Thread.currentThread());
    }

    public static EHSingleton getSingleton() {
        return singleton;
    }
}

不管有没有用到,都直接创建好,好处是没有线程安全的问题,坏处是浪费内存空间。

双检锁 - 线程安全

public class DoubleCheck {
    private static DoubleCheck singleton;

    //私有构造方法
    private DoubleCheck() {
        System.out.println("DoubleCheck init:" + Thread.currentThread());
    }

    public static DoubleCheck getSingleton() {
        if (singleton == null) {
            synchronized (DoubleCheck.class) {
                if (singleton == null) {
                    singleton = new DoubleCheck();
                }
            }
        }
        return singleton;
    }
}

双检锁,又叫双重校验锁,综合了懒汉式和饿汉式两者的优缺点整合而成。看上面代码实现中,特点是在synchronized关键字内外都加了一层 if 条件判断,这样既保证了线程安全,又比直接上锁提高了执行效率,还节省了内存空间。

静态内部类 - 线程安全

public class StaticSingleton {

    public static class StaticSingletonHolder {
        private static final StaticSingleton INSTANCE = new StaticSingleton();
    }

    //私有构造方法
    private StaticSingleton() {
        System.out.println("StaticSingleton init:" + Thread.currentThread());
    }

    public static StaticSingleton getInstance() {
        return StaticSingletonHolder.INSTANCE;
    }
}

静态内部类的方式效果类似双检锁,但实现更简单。但这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用

枚举 - 线程安全

public enum EnumSingleton {
    INSTANCE;

    EnumSingleton() {
        System.out.println("EnumSingleton init:" + Thread.currentThread());
    }
}

什么是线程安全?
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

或者说:一个类或者程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,那就是线程安全的。

实际中的应用

Glide图片加载框架

  //Glide.java
  @GuardedBy("Glide.class")
  private static volatile Glide glide;
  public static Glide get(@NonNull Context context) {
    if (glide == null) {
      GeneratedAppGlideModule annotationGeneratedModule =
          getAnnotationGeneratedGlideModules(context.getApplicationContext());
      synchronized (Glide.class) {
        if (glide == null) {
          checkAndInitializeGlide(context, annotationGeneratedModule); //初始化glide对象
        }
      }
    }
    return glide;
  }

可以很明显的看出采用了双检锁的单例模式。而且glide加了volatile 关键字。

后面有遇到其他使用单例模式的会继续补充…

借鉴

https://blog.csdn.net/jason0539/article/details/23297037

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值