注册表式单例模式详解java_设计模式系列之单例模式

本文详细介绍了Java中单例模式的实现方式,包括饿汉式、静态内部类、懒汉式、双重校验锁和注册表式,以及JDK和Spring框架中的单例模式应用。并讨论了线程安全、序列化和优化策略。
摘要由CSDN通过智能技术生成

本文循序渐进介绍单例模式的几种实现方式,以及Jdk中使用到单例模式的例子,以及sring框架中使用到的单例模式例子。

饿汉式

package signgleton;

/**

* 单例模式简单的实现

*/

public class Singleton {

private static Singleton instance = new Singleton();

private Singleton() {

}

public static Singleton getInstance() {

return instance;

}

}

”饿汉式“只是形象的比喻,因为他想要这个实例的时候,不需要等待,别废话,给哥拿来。通过static的初始化方式,借助类第一次被加载时,就把Singleton实例给创建出来了,并存储在JVM的方法区,属于类变量,被所有的实例共享。不同的线程调用都返回一个实例,所以这样也保证了线程安全。

它还有个孪生兄弟,静态代码块来实例化:

package signgleton;

/**

* 通过静态代码块创建实例对象

*/

public class StaticSignleton {

private static StaticSignleton instance;

/**

* 静态代码块创建实例

*/

static {

instance = new StaticSignleton();

}

private StaticSignleton() {

}

public static StaticSignleton getInstance() {

return instance;

}

}

科普一下类初始化顺序:

静态变量、静态代码块初始化

构造函数

自定义构造函数

饿汉式缺点:因为在类被加载的时候对象就会被实例化,这可能会造成不必要的消耗,如果你的程序不在乎这点消耗那就当我没说。

下面介绍两种方式解决上面的问题:第一是使用静态内部类,第二是使用懒汉式

静态内部类

package signgleton;

/**

* 使用静态内部类获取单例实例

*/

public class StaticInnerClassSingleton {

private static class InnerSingletonClass{

private static final StaticInnerClassSingleton innerInstance = new StaticInnerClassSingleton();

}

private StaticInnerClassSingleton() {

}

public static final StaticInnerClassSingleton getStaticInstance() {

return InnerSingletonClass.innerInstance;

}

}

静态内部类同样借助了JVM这个大佬来保证线程安全,只是他在类加载的时候并没有立即实例化对象,而是采用了延迟加载策略,只有调用getStaticInstance的时候才用内部类去创建实例

线程不安全的懒汉式

package signgleton;

/**

* 线程不安全的懒汉式

*/

public class UnsafeSingleton {

private static UnsafeSingleton unsafeSingleton;

private UnsafeSingleton() {

}

public static UnsafeSingleton getUnsafeSingleton() {

if (unsafeSingleton == null) {

unsafeSingleton = new UnsafeSingleton();

}

return unsafeSingleton;

}

}

虽然这样写达到了使用的时候才实例化的目的,但是也带来的线程安全问题。在多线程下,可能有两个以上的线程同时进入if(unsafeInstance == null),这样会发生一些奇怪不定的结果。

线程安全的懒汉式

package signgleton;

/**

* 线程安全的懒汉式

*/

public class SafeSingleton {

private static SafeSingleton safeSingleton;

private SafeSingleton() {

}

public static synchronized SafeSingleton getSafeSingleton() {

if (safeSingleton == null) {

safeSingleton = new SafeSingleton();

}

return safeSingleton;

}

}

这种方式在方法上加synchronized同步关键字解决了饿汉式线程安全问题,但是因为每次调用都加锁,极大地降低了性能,因为只有第一次创建实例时需要加锁,弄成现在每次都加锁。有没有解决办法呢,当然有,前辈们都是很聪明的,想出了双重校验锁这个经典的例子.

双重校验锁

package signgleton;

/**

* 线程不安全双重校验锁

*/

public class UnSafeTwoCheckSingleton {

private static UnSafeTwoCheckSingleton singleton;

private UnSafeTwoCheckSingleton() {

}

public static UnSafeTwoCheckSingleton getSingleton() {

if (singleton == null) {

synchronized (UnSafeTwoCheckSingleton.class) {

if (singleton == null) {

singleton = new UnSafeTwoCheckSingleton();

}

}

}

return singleton;

}

}

双重校验锁的形式主要是缩小了锁的范围,但是熟悉多线程编程的同学就可以看得出来,即使这样做还是有线程安全问题,这里存在一个多个线程共享变量的可见性问题(这部分我不太懂原理),解决方案就是使用volatile

使用volatile优化

package signgleton;

/**

* 线程安全双重校验锁

*/

public class SafeTwoCheckSingleton {

private static volatile SafeTwoCheckSingleton singleton;

private SafeTwoCheckSingleton() {

}

public static SafeTwoCheckSingleton getSingleton() {

if (singleton == null) {

synchronized (SafeTwoCheckSingleton.class) {

if (singleton == null) {

singleton = new SafeTwoCheckSingleton();

}

}

}

return singleton;

}

}

你以为这样就安全了吗,就想下班了吗?还没完,序列化这个恶棍会破坏单例,防范序列化这个恶棍破坏单例,可以在类中定义我们获取实例的策略,既加readResolve。

防范序列化破坏单例

package signgleton;

import java.io.Serializable;

/**

* 线程安全双重校验锁

*/

public class SafeTwoCheckSingleton implements Serializable{

private static volatile SafeTwoCheckSingleton singleton;

private SafeTwoCheckSingleton() {

}

public static SafeTwoCheckSingleton getSingleton() {

if (singleton == null) {

synchronized (SafeTwoCheckSingleton.class) {

if (singleton == null) {

singleton = new SafeTwoCheckSingleton();

}

}

}

return singleton;

}

private Object readResolve() {

return singleton;

}

}

单例模式案例

这么多的实现方式,你会问,有什么用?用处可大了,下面讲两个使用实例,一个jdk的Runtime, 一个是Spring框架中的单例模式。

Runtime:是一个封装了JVM进程的类,每一个JAVA程序实际上都是JVM的一个进程,每一个进程都是对应这么一个Runtime实例。

源码如下:

public class Runtime {

private static Runtime currentRuntime = new Runtime();

public static Runtime getRuntime() {

return currentRuntime;

}

private Runtime() {}

}

这里使用了饿汉式单例模式。

下面我们来看看看spring 中的单例模式,spring中使用的是单例注册表的特殊方式实现的单例模式,所以说模式是死的,需要灵活得运用。

看看单例注册表的实现原理demo:

package signgleton;

import java.util.Map;

import java.util.concurrent.ConcurrentHashMap;

/**

* 单例注册表demo

*/

public class SingletonRegTest {

private final static Map singletonObjects = new ConcurrentHashMap();

/**

* 类加载时初始化一个实例

*/

static {

SingletonRegTest singletonRegTest = new SingletonRegTest();

singletonObjects.put(singletonRegTest.getClass().getName(), singletonRegTest);

}

public static SingletonRegTest getInstance(String name) {

if (name == null) {

// 默认分配一个实例

name = "signgleton.SingletonRegTest";

}

if (singletonObjects.get(name) == null) {

try {

// 将默认实例放入缓存中

singletonObjects.put(name, Class.forName(name).newInstance());

} catch (Exception ex) {

ex.printStackTrace();

}

}

return (SingletonRegTest) singletonObjects.get(name);

}

}

再来看看spring 源码:

public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {

@SuppressWarnings("unchecked")

protected T doGetBean(

final String name, final Class requiredType, final Object[] args, boolean typeCheckOnly)

throws BeansException {

final String beanName = transformedBeanName(name);

Object bean;

// 从单例注册表中检查是否存在单例缓存

Object sharedInstance = getSingleton(beanName);

if (sharedInstance != null && args == null) {

...

// 返回缓存实例

bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);

}

else {

...

try {

...

// 如果是单例模式

if (mbd.isSingleton()) {

sharedInstance = getSingleton(beanName, new ObjectFactory() {

@Override

public Object getObject() throws BeansException {

try {

return createBean(beanName, mbd, args);

}

catch (BeansException ex) {

...

}

}

});

bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);

}

// 如果是原型模式

else if (mbd.isPrototype()) {

...

}

// 其他模式

else {

...

}

}

catch (BeansException ex) {

...

}

}

return (T) bean;

}

}

我们进入 getSingleton()方法:

import java.util.Map;

public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {

// 通过 ConcurrentHashMap 实现单例注册表

private final Map singletonObjects = new ConcurrentHashMap(64);

public Object getSingleton(String beanName, ObjectFactory> singletonFactory) {

Assert.notNull(beanName, "'beanName' must not be null");

synchronized (this.singletonObjects) {

// 检查缓存中是否存在实例

Object singletonObject = this.singletonObjects.get(beanName);

if (singletonObject == null) {

...

try {

singletonObject = singletonFactory.getObject();

}

catch (BeanCreationException ex) {

...

}

finally {

...

}

// 如果实例对象在不存在,我们注册到单例注册表中。

addSingleton(beanName, singletonObject);

}

return (singletonObject != NULL_OBJECT ? singletonObject : null);

}

}

protected void addSingleton(String beanName, Object singletonObject) {

synchronized (this.singletonObjects) {

this.singletonObjects.put(beanName, (singletonObject != null ? singletonObject : NULL_OBJECT));

}

}

}

是不是和我们的单例注册表demo很相似。

单例模式的讲解后面随着学习到其他框架再做相应的补充,也欢迎大家献言献策。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值