正文
01 为啥再次重写、再次介绍单利模式
单例模式在平时的工作中经常会用到的一种模式,例如在Spring框架中,就会有Singleton的模式创建对象。单例模式是指确保一个类在任何的情况下绝对只有一个实例。本内容将围绕如何创建单例模式以及真实代码结合的方式介绍单例模式,作为自己的总结,分享给大家!
02 饿汉式单例模式
饿汉式单例模式指的是在类加载的时候,就会立即进行初始化单例对象。它绝对线程安全,因为在线程没出现就已经初始化了,不可能存在访问安全的问题。饿汉式单例模式代码如下:
public class HungrySingleton { private HungrySingleton() { // 私有化构造方法 // 防止外部直接new 得到实例的对象 } private static final HungrySingleton INSTANCE = new HungrySingleton(); public static HungrySingleton getInstance() { return INSTANCE; } }
03 懒汉式单例模式
懒汉式单例模式与饿汉式单例模式相对应,指的是在没有用到它之前,不会进行初始化,真实使用的时候会进行初始化以备使用。下面给出懒汉式单例模式的代码(这里直接给出最终的正确的模式,直接采用Double Check模式):
public class LazySingleton { private LazySingleton() { // 私有化构造方法 // 防止外部直接new 得到实例的对象 } private static volatile LazySingleton INSTANCE; // 懒加载的模式 public static LazySingleton getInstance() { if (null == INSTANCE) { synchronized (LazySingleton.class) { if (null == INSTANCE) { // 初始化对象 INSTANCE = new LazySingleton(); // ... // 设置其他的数值 } } } return INSTANCE; } }
04 懒汉式单例模式升级版
上面的代码,采用 synchronized 关键字防止其他线程初始化单例对象。有没有更好的方法不使用这个关键字也可以构造简单,安全的饿汉式单利模式呢?答案是可以的,可以采用静态内部类的方式实现。静态内部类,在我们没有使用的时候,是不会主动初始化的,只有当外部调用的时候才会初始化,请看下面的代码:
public class LazyInnerSingleton { private LazyInnerSingleton() { // 私有化构造方法 // 防止外部直接new 得到实例的对象 } // 懒加载的模式 public static final LazyInnerSingleton getInstance() { // 返回结果前,会先主动去加载内部类的 return LazyHolder.INSTANCE; } private static final class LazyHolder { private static final LazyInnerSingleton INSTANCE = new LazyInnerSingleton(); } }
05 枚举式单例模式
大家都知道,枚举的构造仅仅会初始化一次,所以可以使用这个知识构造我们的单利:
public enum EnumSingleton { INSTANCE; private Singleton singleton; EnumSingleton() { singleton = new Singleton(); } public static Singleton getInstance() { return INSTANCE.singleton; } }
测试:
06 容器式单例模式
容器式单例模式也是经常使用的,它可以有效的管理项目中、系统中的单例,下面看实现的代码:
import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class ContainerSingleton { private ContainerSingleton() { // 私有化构造方法 // 防止外部直接new 得到实例的对象 } private static Map<String, Object> map = new ConcurrentHashMap<>(); public static Object getInstance(String className) { if (!map.containsKey(className)) { Object obj = null; try { obj = Class.forName(className).newInstance(); map.put(className, obj); } catch (Exception e) { e.printStackTrace(); } return obj; } return map.get(className); } }
上面容器式单例模式是线程非安全的,使用的时候要注意,可以采用DL方式变成一个线程安全的模式,不过也要注意创建用例的性能。
07 线程内单例模式
有的时候,我们或许不需要全程的单例,仅仅需要在某一个线程内部实现单例即可,可以采用ThreadLocal工具实现这个功能。代码如下:
import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class ThreadLocalSingleton { private ThreadLocalSingleton() { // 私有化构造方法 // 防止外部直接new 得到实例的对象 } private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance = new ThreadLocal<ThreadLocalSingleton>() { // 仅仅会调用一次 // 下一次调用是本线程没有这个对象了 @Override protected ThreadLocalSingleton initialValue() { return new ThreadLocalSingleton(); } }; private static Map<String, Object> map = new ConcurrentHashMap<>(); public static Object getInstance() { return threadLocalInstance.get(); } }
测试:
08 其他思考
当前所有的单例模式都是采用自己的意志构建并使用的。那用户不按照我们的思路创建单例,是否破坏单例模式呢?答案是肯定的,是可以采用其他的手段破坏的。方法如下,感兴趣的可以自己研究。
方法一:利用反射破坏单例的创建过程,强制设置构造方法可以访问
方法二:序列化与反序列化的过程,反序列化就会得到不一样的地址空间的对象,构造了不一样的对象,破坏了单例模式。