资讯专栏INFORMATION COLUMN

设计模式系列之单例模式

Jason / 3382人阅读

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

本文循序渐进介绍单例模式的几种实现方式,以及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很相似。
单例模式的讲解后面随着学习到其他框架再做相应的补充,也欢迎大家献言献策。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/75033.html

相关文章

  • JavaScript设计模式与开发实践系列单例模式

    摘要:本系列为设计模式与开发实践作者曾探学习总结,如想深入了解,请支持作者原版单例模式实现单例模式单例模式的定义是保证一个类仅有一个实例,并提供一个访问它的全局访问点。 本系列为《JavaScript设计模式与开发实践》(作者:曾探)学习总结,如想深入了解,请支持作者原版 单例模式 实现单例模式 单例模式的定义是:保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例模式是一种常用的模式...

    Airy 评论0 收藏0
  • 【php实现设计模式单例模式

    摘要:单例模式是最常用,也是最简单的一种设计模式。什么是单例模式他是一个特殊的类,该类在系统运行时只有一个实例。这个类必须提供一个获取对象实例的方法。可以参考鸟哥的这遍文章,经测试在下是有效的破坏单例 单例模式是最常用,也是最简单的一种设计模式。 什么是单例模式他是一个特殊的类,该类在系统运行时只有一个实例。这个类必须提供一个获取对象实例的方法。 有什么作用1.全局只创建一次实例,提高性能,...

    shery 评论0 收藏0
  • 大话PHP设计模式单例模式升级版

    摘要:用来指向已创建好的实例构造函数为空注意这里是关键这是我们需要调用的方法把函数也定义为空,这样就大功告成啦。 接上一篇大话PHP设计模式之单例模式 这一篇介绍一下升级版的单例模式,废话不说先上代码 不完美的单例模式 class singleMode { //用来指向已创建好的实例 public static $instance; //判断是...

    darcrand 评论0 收藏0
  • 每天一个设计模式单例模式

    摘要:博主按每天一个设计模式旨在初步领会设计模式的精髓,目前采用靠这吃饭和纯粹喜欢两种语言实现。单例模式用途如果一个类负责连接数据库的线程池日志记录逻辑等等,此时需要单例模式来保证对象不被重复创建,以达到降低开销的目的。 博主按:《每天一个设计模式》旨在初步领会设计模式的精髓,目前采用javascript(_靠这吃饭_)和python(_纯粹喜欢_)两种语言实现。诚然,每种设计模式都有多种实...

    yy736044583 评论0 收藏0
  • 每天一个设计模式单例模式

    摘要:博主按每天一个设计模式旨在初步领会设计模式的精髓,目前采用靠这吃饭和纯粹喜欢两种语言实现。单例模式用途如果一个类负责连接数据库的线程池日志记录逻辑等等,此时需要单例模式来保证对象不被重复创建,以达到降低开销的目的。 博主按:《每天一个设计模式》旨在初步领会设计模式的精髓,目前采用javascript(_靠这吃饭_)和python(_纯粹喜欢_)两种语言实现。诚然,每种设计模式都有多种实...

    lijy91 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<