前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >单例设计模式(java与node实现)

单例设计模式(java与node实现)

作者头像
许喜朝
发布2020-11-26 17:18:14
6280
发布2020-11-26 17:18:14
举报
文章被收录于专栏:生如夏花的个人博客

单例设计模式

什么是单例设计模式

单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例

具体实现

(1)将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象。

(2)在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型。

(3)定义一个静态方法返回这个唯一对象。

java语言实现
懒汉模式

延迟加载,当只有使用的时候才开始真正的实例化

代码语言:javascript
复制
/**
 * 单例设计模式懒汉式
 */
public class SingleTonLazy {
    //定义一个实例化对象
    private static SingleTonLazy singleTonLazy = null;

    //构造方法私有化
    private SingleTonLazy(){}
    //静态工厂方法
    private static SingleTonLazy getInstance(){
        if (singleTonLazy == null){
            singleTonLazy = new SingleTonLazy();
        }
        return singleTonLazy;
    }
}

单例设计模式本身是线程安全的,因为从程序被创建就提供了一个静态类,除非关闭程序

但是在多线程模式下就会出现线程安全问题

加锁单例
代码语言:javascript
复制
public class SingleTonSync {
    private static SingleTonSync singleTonSync = null;
    //构造方法私有
    private SingleTonSync(){}

    public static SingleTonSync getSingleTonSync(){
        if (singleTonSync == null){
            //加锁操作
            synchronized (SingleTonSync.class){
                if (singleTonSync == null){
                    singleTonSync = new SingleTonSync();
                }
            }
        }

        return singleTonSync;
    }

}

当singleTonSync对象是空时进行加锁操作,来保证线程的安全

在编译器,cpu进行编译时有可能对指令进行重排序,导致尚未初始化的示例

什么意思呢?

创建对象的正常顺序:1分配空间,2初始化,3引用赋值

被重排序以后:1分配空间,3引用赋值,2初始化

假入有两个线程 T1,T2

T1首次创建对象被重排序以后,T2有可能在对象引用赋值之后,初始化之前访问,此时singleTonSync不为空 T2线程就直接返回singleTonSync对象,但是由于没初始化就会发生空指针等异常 那么如何来处理呢?

可以通过volatile关键字修饰,对于volatile修饰的字段,可以防止指令重排序

防止重排序
代码语言:javascript
复制
//加入volatile 对于volatile修饰的字段,可以防止指令重排序
private volatile static SingleTonSync singleTonSync = null;
//构造方法私有
private SingleTonSync(){}

public static SingleTonSync getSingleTonSync(){
    if (singleTonSync == null){
        //加锁操作
        synchronized (SingleTonSync.class){
            if (singleTonSync == null){
                singleTonSync = new SingleTonSync();
            }
        }
    }

    return singleTonSync;
}
饿汉模式
代码语言:javascript
复制
class SingleTon{
    private static SingleTon singleTon = new SingleTon();

    private SingleTon(){}

    public static SingleTon getSingleTon(){
        return singleTon;
    }
}

通过jvm的类加载机制来保证单例

静态内部类
代码语言:javascript
复制
class SingleTonStaticTest{
    //静态内部类
    private static class SingleTon{
        private static SingleTonStaticTest singleTonStaticTest = new SingleTonStaticTest();
    }
    private SingleTonStaticTest(){}

    public static SingleTonStaticTest getInstance(){
        return SingleTon.singleTonStaticTest;
    }
}

本质上是利用类的加载机制来保证线程的安全

只有在实际使用时才会触发类的初始化,所以也是懒加载的一种

反射创建单例对象的问题

通过反射来创建类会破坏单例

代码语言:javascript
复制
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
  //通过反射来创建单例对象
    Constructor<SingleTonStaticTest> sin = SingleTonStaticTest.class.getDeclaredConstructor();
    sin.setAccessible(true);
    SingleTonStaticTest singleTonStaticTest = sin.newInstance();
    SingleTonStaticTest instance = SingleTonStaticTest.getInstance();
    System.out.println(singleTonStaticTest == instance);
}

这里的两个对象就不是单例对象

那么怎么来解决呢?

懒汉模式是不能解决的,懒汉模式的单例对象应当避免使用反射的方式创建

饿汉模式和静态内部类可以通过异常处理解决

代码语言:javascript
复制
class SingleTonStaticTest{
    //静态内部类
    private static class SingleTon{
        private static SingleTonStaticTest singleTonStaticTest = new SingleTonStaticTest();
    }
    private SingleTonStaticTest(){
        //解决反射创建单例对象的问题
        if (SingleTon.singleTonStaticTest!=null){
            throw new RuntimeException("单例模式不允许创建多个对象");
        }
    }
    public static SingleTonStaticTest getInstance(){
        return SingleTon.singleTonStaticTest;
    }
}

那么是不是所有的类都能通过反射创建呢?

查看newInstance的源码发现

代码语言:javascript
复制
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
    throw new IllegalArgumentException("Cannot reflectively create enum objects");

当类型为枚举时是会抛出一个异常的

枚举类型
代码语言:javascript
复制
/**
 * 枚举类型单例
 */
public enum SingleTonEnmu {
    INSTANCE;
}
反序列化创建对象的问题

话不多说看代码

代码语言:javascript
复制
public class SingleTonStatic {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {
      	//获取到单例对象
        SingleTonStaticTest instance = SingleTonStaticTest.getInstance();
     		//序列化对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
        oos.writeObject(instance);
        oos.close();
      	//反序列化对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"));
        SingleTonStaticTest singleTonStaticTest = ((SingleTonStaticTest) ois.readObject());
        
      //输出false
      System.out.println(instance == singleTonStaticTest);

    }
}
class SingleTonStaticTest implements Serializable {
    //静态内部类
    private static class SingleTon{
        private static SingleTonStaticTest singleTonStaticTest = new SingleTonStaticTest();
    }
    private SingleTonStaticTest(){
        //解决反射创建单例对象的问题
        if (SingleTon.singleTonStaticTest!=null){
            throw new RuntimeException("单例模式不允许创建多个对象");
        }
    }
    public static SingleTonStaticTest getInstance(){
        return SingleTon.singleTonStaticTest;
    }
}

反序列化创建对象字节流中获取数据不会通过类的构造函数

那么如何解决?

查看Serializable接口的源码

代码语言:javascript
复制
* Classes that need to designate a replacement when an instance of it
* is read from the stream should implement this special method with the
* exact signature.
*
* <PRE>
* ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
* </PRE><p>

提供一个readResolve方法

改造反序列化创建对象
代码语言:javascript
复制
public class SingleTonStatic {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException, ClassNotFoundException {
				//获取到单例对象
        SingleTonStaticTest instance = SingleTonStaticTest.getInstance();
     		//序列化对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
        oos.writeObject(instance);
        oos.close();
      	//反序列化对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"));
        SingleTonStaticTest singleTonStaticTest = ((SingleTonStaticTest) ois.readObject());
        
      //输出true
      System.out.println(instance == singleTonStaticTest);

    }
    }
}
class SingleTonStaticTest implements Serializable {
  	//序列化版本号
    static final long serialVersionUID = 42L;
    //静态内部类
    private static class SingleTon{
        private static SingleTonStaticTest singleTonStaticTest = new SingleTonStaticTest();
    }
    private SingleTonStaticTest(){
        //解决反射创建单例对象的问题
        if (SingleTon.singleTonStaticTest!=null){
            throw new RuntimeException("单例模式不允许创建多个对象");
        }
    }
    public static SingleTonStaticTest getInstance(){
        return SingleTon.singleTonStaticTest;
    }
  //返回自己的类
    Object readResolve() throws ObjectStreamException{
        return SingleTon.singleTonStaticTest;
    }
}
反序列化枚举类型

刚刚提到了单例对象可以通过反序列化被破坏,那么枚举类型会怎样呢?

查看反序列枚举类型的实现

查看readObject方法源码

代码语言:javascript
复制
//拿到对应的枚举类型
String name = readString(false);
Enum<?> result = null;
Class<?> cl = desc.forClass();
if (cl != null) {
    try {
        @SuppressWarnings("unchecked")
      //获取对应的实例
        Enum<?> en = Enum.valueOf((Class)cl, name);
        result = en;
    } catch (IllegalArgumentException ex) {
        throw (IOException) new InvalidObjectException(
            "enum constant " + name + " does not exist in " +
            cl).initCause(ex);
    }
    if (!unshared) {
        handles.setObject(enumHandle, result);
    }
}

通过源码我们可以看到valueof方法是一个静态方法

代码语言:javascript
复制
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
                                            String name) {
    T result = enumType.enumConstantDirectory().get(name);
    if (result != null)
        return result;
    if (name == null)
        throw new NullPointerException("Name is null");
    throw new IllegalArgumentException(
        "No enum constant " + enumType.getCanonicalName() + "." + name);
}

综上枚举类型的单例对象对反序列有着天然的优势,换句话讲 通过反序列化的方式不会破坏枚举类型的单例对象

单例对象在jdk中的应用

java.lang.Runtime

代码语言:javascript
复制
public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}
    }

饿汉模式在jdk中的实现

node实现
单例核心代码
代码语言:javascript
复制
// 构造函数

function Person(){
    this.name = '张三'
}
// 单例核心代码
let instance = null
function singleTon(){
    if(!instance) instance = new Person()

    return instance
}
使用闭包对单例模式进行改造
代码语言:javascript
复制
//使用闭包对单例核心代码进行改造

const singleTon = ( function(){
    // 构造函数
    function Person(){
        this.name = '张三'
    }
    let instance = null
    return function singleTon(){
        if(!instance) instance = new Person()

        return instance
    }
})()
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/11/24 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 单例设计模式
    • 什么是单例设计模式
      • 具体实现
        • java语言实现
          • node实现
          相关产品与服务
          文件存储
          文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档