单例模式(Singleton)指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。举个例子:屏幕前的你幻想一下现在你有5个漂亮的老婆,他们的老公都是你,那么你就是家里的Singleton,5个老婆喊“老公”(全局访问点),都是指向一个人那就是你。这样理解清楚了吧?清楚了那么赶紧醒醒回到现实,不要想老婆的事了,跟着老师继续学习,女人只会影响我们撸码的速度~
在J2EE标准中的 ServletContext 和 ServletContextConfig、Spring 框架应用中的 ApplicationContext、数据库中的连接池等也都是单例模式。
饿汉单例模式在类加载的时候就立即初始化,并且创建静态单例对象供系统使用,我们常说饿汉单例模式是线程安全的,因为在线程还没出现它就已经实例化了,不会存在访问安全问题,直接用于多线程也不会出现问题。我知道又有人要说了,哎呀你说这些理论东西,我也理解不来,那么还是一个的例子:现在要进行产品的销售,加载类时,先把商品(实例)准备好,现在你需要,我直接给你。
//常规写法
public class HungrySingleton {
private static final HungrySingleton instance = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return instance;
}
}
还有一种可以利用代码块机制的写法
//利用静态代码块的写法
public class HungryStaticSingleton {
private static final HungryStaticSingleton hungryStaticSingleton;
static {
hungryStaticSingleton = new HungryStaticSingleton();
}
private HungryStaticSingleton() {
}
public static HungryStaticSingleton getInstance() {
return hungryStaticSingleton;
}
}
测试是不是唯一实例,可以调用getInstance的hashCode方法验证结果是不是一致
//测试是否唯一 不会新new一个实例
public static void main(String[] args) {
System.out.println(HungryStaticSingleton.getInstance().hashCode());
System.out.println(HungryStaticSingleton.getInstance().hashCode());
}
从以上描述我们可以看出饿汉单例模式的优点:线程安全,执行效率高;但是同样因为类加载的时候就已经初始化,不管你用不用它都在内存占用着,形象说就是“占着茅坑不拉屎”,这个也是它的缺点。
懒汉单例模式:类加载时没有生成单例,当外部类调用的时候内部类才会加载(getlnstance)去创建这个单例。以代码展现简单实现:
public class LazySingleton {
private LazySingleton(){}
private static LazySingleton lazy = null;
public static LazySingleton getInstance(){
if(lazy==null){
lazy = new LazySingleton();
}
return lazy;
}
}
上面的代码在单线程中不会出现问题,如果在多线程应用中,线程是不安全的,还是销售产品来说,现在产品不是按照饿汉单例模式了,是你预定了我才给你,那么此时有两个顾客A(线程A)和顾客B(线程B),这个时候顾客A预定了,调用getInstance()方法,假设程序还没走完,顾客B又预定了,原本按照代码是只要lazy 存在是不会再new LazySingleton()的,但是因为顾客A调用的getInstance()还没执行完成,lazy==null,而顾客B又预定了,即可以理解是new 了两次LazySingleton,破坏了破坏了单例。
如何解决?就是在getInstance()加synchronized关键字,从而使得线程同步
public class LazySyncSingleton {
private static LazySyncSingleton lazy = null;
private LazySyncSingleton(){}
public synchronized static LazySyncSingleton getInstance(){
if(lazy==null){
lazy = new LazySyncSingleton();
}
return lazy;
}
}
上述代码可以解决线程安全问题,也就是顾客A预定了,就必须等顾客A拿到产品,顾客B才可以预定,因为synchronized是线程阻塞的,在线程数量较多的情况下,会导致大量线程阻塞,解决这个方式也很简单,双重检查的方式,也即是A顾客预定产品,在没拿到产品前,B顾客不需要等待已久可以预定,因为synchronized是在getInstance()内部阻塞,只要getInstance()内部逻辑不复杂,在顾客A拿到产品的同时,顾客B几乎无感知也拿到产品了。
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton lazy = null;
private LazyDoubleCheckSingleton(){}
//双重检查
public static LazyDoubleCheckSingleton getInstance(){
if(lazy==null){
synchronized(LazyDoubleCheckSingleton.class){
if(lazy==null) {
lazy = new LazyDoubleCheckSingleton();
}
}
}
return lazy;
}
}
以上方式加了synchronized关键字,无论如何都会加锁,对程序而言性能依旧存在影响的,那么是否有完美的解决方案?答案是有的,不知道大家想到饿汉模式的静态内部类方式了么?懒汉模式搭配静态内部类方式可以完美解决
//最优的单例模式写法
public class LazyStaticSingleton {
private LazyStaticSingleton(){
if(lazyHolder.LAZY!=null){
throw new RuntimeException("不允许创建多个实例");
}
}
private static final LazyStaticSingleton getInstance(){
//return之前会先加载内部类lazyHolder
return lazyHolder.LAZY;
}
//默认不会加载
private static class lazyHolder{
private static final LazyStaticSingleton LAZY = new LazyStaticSingleton();
}
}
以上写法即满足了饿汉模式内存占用“占着茅坑不拉屎”,也解决了synchronized性能的问题。
注册式单例模式意思是将每个实例都注册登记起来,给一个唯一标示来获取,还是产品销售的例子,顾客A预定产品,这个产品就标记起来(A货架),顾客B预定产品如果是相同产品就按照标示(A货架)直接返回产品,即实例。
注册式单例模式有两种:枚举单例模式 + 容器单例模式
//枚举式单例
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumSingleton getInstance() {
return INSTANCE;
}
}
//容器式单例模式
public class ContainerSingleton {
private ContainerSingleton() {}
private static final Map<String, Object> ioc = new ConcurrentHashMap<>();
public static Object getBean(String className) {
synchronized (ioc) {
if (!ioc.containsKey(className)) {
Object obj = null;
try {
obj = Class.forName(className).newInstance();
ioc.put(className, obj);
} catch (Exception e) {
e.printStackTrace();
}
return obj;
} else {
return ioc.get(className);
}
}
}
}
枚举式单例模式:推荐单例模式的实现写法;
容器式单例模式:使用于实例非常多的情况,便于管理。(Spring中也有应用到该模式,查阅抽象类 AbstractAutowireCapableBeanFactory)
具体大家可以搜索Spring源码查阅。
本文主要介绍了设计模式的单例模式,例举了多种单例模式的实现方式,在日常开发中博主推荐大家使用枚举式单例模式,通过本章节希望对大家对单例模式有一个更清晰的理解;
样例代码:https://github.com/lhmyy521125/toher-designmode
下期预告:津津乐道设计模式 - 代理模式详解