一文穿透单例模式

一、什么是单例模式

允许存在一个和仅存在一个给定类的实例,并提供了一个全局访问点供外部获取该实例。
一般分为两大类:

  • 懒汉模式:实例在第一次使用时创建
  • 饿汉模式:实例在类加载时创建

二、为什么要使用单例模式

1、资源有限。比如数据库的访问权限,如果不限制实例的数量,那么有限的资源很快就会耗尽,同时造成大量的对象处于等待状态。
2、全局唯一对象。比如系统要求提供一个唯一的序列号生成器。

三、UML图

比如生成全局唯一序列号的UML图如下所示
在这里插入图片描述

四、代码实现

要实现一个单例模式,谨记三点:
1、构造函数私有。
2、内部持有一个私有的静态单例模式的实例。
3、提供一个公共的静态方法用于获取单例对象。

实现进程间唯一的方式

1、饿汉式单例
public final class Singleton {
   /**
    * 使用private将构造方法私有化,以防外界通过该构造方法创建多个实例
    */
    private Singleton() {}

    /**
     * 饿汉式:不支持延迟加载
     * 优点:类加载都内存后,就实例化一个单例,JVM保证线程安全,简单实用,推荐使用
     * 缺点:
     *      由于instance的初始化是在类加载时进行的,类加载是由ClassLoader来实现的,如果初始化太早,就会造成资源浪费
     * 类加载的时机:
     *      1、new一个对象时
     *      2、使用反射创建它的实例时
     *      3、子类被加载时,如果父类还没有被加载,就先加载父类
     *      4、jvm启动时执行主类会先被加载
     *
     */
    // 私有化的类成员变量
    private static final Singleton instance = new Singleton();

    // 公共的类实例的访问方法
    public static Singleton getInstance() {
        return instance;
    }

    private AtomicLong atomicLong = new AtomicLong(0);

    public long getId() {
        return atomicLong.incrementAndGet();
    }
}
2、懒汉式单例
public final class Singleton {
   /**
    * 使用private将构造方法私有化,以防外界通过该构造方法创建多个实例
    */
    private Singleton() {}

   /**
    * 懒汉式:支持延时加载
    * 有性能瓶颈
    */
    private static volatile Singleton instance = null;

    // 方法级别的锁,避免多线程导致多个实例的情况,影响效率
    public static synchronized Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }

    private AtomicLong atomicLong = new AtomicLong(0);

    public long getId() {
        return atomicLong.incrementAndGet();
    }
}
3、双重校验锁单例
public final class Singleton {
   /**
    * 使用private将构造方法私有化,以防外界通过该构造方法创建多个实例
    */
    private Singleton() {}

   /**
    * 懒汉式
    * volatile的作用是禁止指令重排
    */
    private static volatile Singleton instance = null;

   /**
    * 双重校验锁
    * 既支持延迟加载、又支持高并发的单例
    */
    public static Singleton getInstance() {
        // 解决上面代码中的效率问题
        if (instance == null) {
            // 类级别的锁
            synchronized (Singleton.class) {
                // 防止可能出现多个实例的情况
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }

    private AtomicLong atomicLong = new AtomicLong(0);

    public long getId() {
        return atomicLong.incrementAndGet();
    }
}
4、CAS实现单例
public final class Singleton {
   /**
    * 使用private将构造方法私有化,以防外界通过该构造方法创建多个实例
    */
    private Singleton() {}

    /**
     * 采用CAS实现单例
     */
    private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<>();

    public static Singleton getInstance() {
        for(;;) {
            Singleton singleton = INSTANCE.get();
            if (singleton != null) {
                return singleton;
            }
            singleton = new Singleton();
            if(!INSTANCE.compareAndSet(null, singleton)) {
                return singleton;
            }
        }
    }

    private AtomicLong atomicLong = new AtomicLong(0);

    public long getId() {
        return atomicLong.incrementAndGet();
    }
}
5、静态内部类实现单例
public final class Singleton {
   /**
    * 使用private将构造方法私有化,以防外界通过该构造方法创建多个实例
    */
    private Singleton() {}

   /**
    * 静态内部类
    * JVM保证单例
    * 对于内部类SingletonHolder,它是一个饿汉式的单例实现,
    * 利用了ClassLoader来保证同步,同时又能让开发者控制类加载的时机
    */
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    /**
     * 从外部来看,是懒汉式式的实现
     */
    public static final Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

    private AtomicLong atomicLong = new AtomicLong(0);

    public long getId() {
        return atomicLong.incrementAndGet();
    }
}
6、枚举实现单例(工作中建议使用)
public enum Singleton {
   /**
    * 枚举实现,最优方法,不仅可以解决线程同步,还可以防止反序列化
    * 使用Singleton.INSTANCE.getId();
    */
    INSTANCE;

    private AtomicLong atomicLong = new AtomicLong(0);

    public long getId() {
        return atomicLong.incrementAndGet();
    }
}

实现线间唯一的方式

7、ThreadLocal实现单例
public class ThreadSingleton {
	
	private ThreadSingleton() {}
	// 适合有大量单例对象需要统一管理的情况
	private static final ThreadLocal<ThreadSingleton>  THREAD_SINGLETON_THREAD_LOCAL= new ThreadLocal<>();
    
    // 延迟初始化
    public static ThreadSingleton getThreadSingleton() {
        ThreadSingleton threadSingleton = THREAD_SINGLETON_THREAD_LOCAL.get();
        if(threadSingleton == null) {
            threadSingleton = init();
        }
        return threadSingleton;
    }

    // 初始化实现
    private static ThreadSingleton init() {
        ThreadSingleton threadSingleton = new ThreadSingleton();
        THREAD_SINGLETON_THREAD_LOCAL.set(threadSingleton);
        return threadSingleton;
    }

    // 删除实例
    public static void remove() {
        THREAD_SINGLETON_THREAD_LOCAL.remove();
    }
    
    private AtomicLong atomicLong = new AtomicLong(0);

    public long getId() {
        return atomicLong.incrementAndGet();
    }

}
8、容器单例
public class ThreadSingleton {
	
	private ThreadSingleton() {}
	// 适合有大量单例对象需要统一管理的情况
	private static final Map<Long, ThreadSingleton> INSTANCE = new ConcurrentHashMap<>();

    public static ThreadSingleton getInstance() {
        Long currentThreadId = Thread.currentThread().getId();
        INSTANCE.putIfAbsent(currentThreadId, new ThreadSingleton());
        return INSTANCE.get(currentThreadId);
    }

    private AtomicLong atomicLong = new AtomicLong(0);

    public long getId() {
        return atomicLong.incrementAndGet();
    }

}

五、优点

1、单例模式可以保证内存里只有一个实例,减少了内存的开销。
2、对有限资源的合理利用,保护有限的资源,避免对资源的多重占用。
3、单例模式设置全局访问点,可以优化和共享的访问。

六、缺点

1、单例模式一般没有接口,扩展性差。
2、在并发测试中,不利于代码调试。
3、作为全局变量使用时,引用的对象越多,代码修改影响范围越大。
4、单例模式的代码一般写在一个类中,如果功能设计不合理,容易违背单一职责原则。

七、应用场景

1、需要频繁创建的一些类,使用单例可以降低系统的内存压力,减少 GC。
2、某类只要求生成一个对象的时候,如每一个公文件的主键、生成唯一序列号ID、任务的任务ID等。
3、某些类创建实例时占用资源较多,或实例化耗时较长,且经常使用。
4、某类需要频繁实例化,而创建的对象又频繁被销毁的时候,如多线程的线程池、数据库连接池、对象池等。
5、频繁访问数据库或文件的对象。如数据库连接池、应用程序的日志对象、系统中的缓存。
6、当对象需要被共享的场合。由于单例模式只允许创建一个对象,共享该对象可以节省内存,并加快对象访问速度。如 Web 中的配置对象、数据库的连接池等。

八、单例源码解析

之前介绍了多种实现单例模式的方法,现在看下JDK和Spring是怎么运用单例模式的。

1、JDK

JDK中java.lang.Runtime类就是一个饿汉式单例模式,它不准外部创建实例

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
        return currentRuntime;
    }

    private Runtime() {}
}
2、Spring

本文主要讲解单例模式,所以直入主题,关于Spring内容,参考后续文章或其他文章。
在spring-beans-5.1.19.RELEASE中,找到SingletonBeanRegistry接口,源码如下:

package org.springframework.beans.factory.config;

import org.springframework.lang.Nullable;

// 注册单例bean接口
public interface SingletonBeanRegistry {
    void registerSingleton(String var1, Object var2);

	// 我们需要关注的方法,找到它的实现类
    @Nullable
    Object getSingleton(String var1);

    boolean containsSingleton(String var1);

    String[] getSingletonNames();

    int getSingletonCount();

    Object getSingletonMutex();
}

找到getSingleton()的实现类代码如下:

// 关于Spring实例化bean可以在写Spring循环依赖和三级缓存详说
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
   private static final int SUPPRESSED_EXCEPTIONS_LIMIT = 100;
   // 单例对象容器  一级缓存
   private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
   // 单例工厂容器 三级缓存
   private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
   // 循环依赖单例对象容器  二级缓存
   private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
   // 已注册的单例列表
   private final Set<String> registeredSingletons = new LinkedHashSet(256);
   // 正在创建的单例列表
   private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap(16));
   
   // 。。。。。。省略部分代码

   @Nullable
   public Object getSingleton(String beanName) {
       return this.getSingleton(beanName, true);
   }
   // 双重校验锁实现单例
   @Nullable
   protected Object getSingleton(String beanName, boolean allowEarlyReference) {
       // 一级缓存,根据bean从单例对象缓存池获取当前对象
       Object singletonObject = this.singletonObjects.get(beanName);
       // 如果当前bean正在创建过程中,而且缓存池中没有则继续
       if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
   	   // 二级缓存
           singletonObject = this.earlySingletonObjects.get(beanName);
   	   // 从缓存池中获取bean实例,如果为null,对单例对象池加锁,然后再从缓存池中获取bean
           if (singletonObject == null && allowEarlyReference) {
   	       // 对单例对象池加锁
               synchronized(this.singletonObjects) {
                   singletonObject = this.singletonObjects.get(beanName);
   		   // 如果还是为null,就创建一个bean
                   if (singletonObject == null) {
                       singletonObject = this.earlySingletonObjects.get(beanName);
                       if (singletonObject == null) {
   			   // 三级缓存
                           ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
                           if (singletonFactory != null) {
                               singletonObject = singletonFactory.getObject();
                               this.earlySingletonObjects.put(beanName, singletonObject);
                               this.singletonFactories.remove(beanName);
                           }
                       }
                   }
               }
           }
       }
       return singletonObject;
   }
   // 。。。。。。省略部分代码
}

注意:Spring并没有私有构造方法,而是通过singletonFactory.getObject()返回具体beanName对应的ObjectFactory来创建bean。

JDK单例模式和Spring单例bean的区别:

  • JDK单例模式:在一个JVM进程中仅有一个实例;
  • Spring单例bean:在一个Spring容器中仅有一个实例。

九、注意点

1、如何破坏单例模式?
  • 序列化
  • 反射
2、如何防止呢?
  • 实现readResolve方法防止序列化机制
public Singleton readResolve() {
	return instance;
}
  • 在私有构造函数内部添加一个成员变量flag标志防止反射二次调用
private Singleton() {
	throw new RuntimeException("禁止通过反射调用!");
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值