学而不思则罔,思而不学则殆
【设计模式】单例模式
简介
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
特点
- 单例类只有一个实例
- 单例类必须自己创建自己(所以构造函数私有)
- 单例类跟其他方提供这个实例
单例实现方式
懒汉式 - 线程不安全
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