专栏首页生如夏花的个人博客单例设计模式(java与node实现)

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

单例设计模式

什么是单例设计模式

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

具体实现

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

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

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

java语言实现

懒汉模式

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

/**
 * 单例设计模式懒汉式
 */
public class SingleTonLazy {
    //定义一个实例化对象
    private static SingleTonLazy singleTonLazy = null;

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

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

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

加锁单例
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修饰的字段,可以防止指令重排序

防止重排序
//加入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;
}
饿汉模式
class SingleTon{
    private static SingleTon singleTon = new SingleTon();

    private SingleTon(){}

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

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

静态内部类
class SingleTonStaticTest{
    //静态内部类
    private static class SingleTon{
        private static SingleTonStaticTest singleTonStaticTest = new SingleTonStaticTest();
    }
    private SingleTonStaticTest(){}

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

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

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

反射创建单例对象的问题

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

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);
}

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

那么怎么来解决呢?

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

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

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的源码发现

if ((clazz.getModifiers() & Modifier.ENUM) != 0)
    throw new IllegalArgumentException("Cannot reflectively create enum objects");

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

枚举类型
/**
 * 枚举类型单例
 */
public enum SingleTonEnmu {
    INSTANCE;
}
反序列化创建对象的问题

话不多说看代码

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接口的源码

* 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方法

改造反序列化创建对象
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方法源码

//拿到对应的枚举类型
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方法是一个静态方法

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

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实现

单例核心代码
// 构造函数

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

    return instance
}
使用闭包对单例模式进行改造
//使用闭包对单例核心代码进行改造

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

        return instance
    }
})()

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • zookeeper完整详细版

    配置java环境,这个教程应该能帮你:https://www.runoob.com/java/java-environment-setup.html

    许喜朝
  • Hbase(二)Hbase常用操作

    许喜朝
  • springboot解决前后端数据跨域问题

    许喜朝
  • 从实例出发,了解单例模式和静态块

    什么是单例模式呢,单例模式(Singleton)又叫单态模式,它出现目的是为了保证一个类在系统中只有一个实例,并提供一个访问它的全局访问点。从这点可以看出,单例...

    古时的风筝
  • php设计模式-单例模式

    单例模式是一种常见的设计模式,在计算机系统中,线程池、缓存、日志对象、对话框、打印机、数据库操作、显卡的驱动程序常被设计成单例。

    仙士可
  • 单例模式(上)

    一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式(Singleton Design Pattern)。

    汐楓
  • 写了这么久代码,你懂单例模式吗?

    这种方式在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快。这种方式基于类加载机制避免了多线程的同步问题,但是也不能确定有其他的方式(或者其他的静态方...

    咻咻ing
  • 设计模式二十四章经之单例设计模式

    我就是马云飞
  • 37个JavaScript基本面试问题和解答(建议收藏)

    1、使用typeof bar ===“object”来确定bar是否是一个对象时有什么潜在的缺陷?这个陷阱如何避免?

    用户1272076
  • 设计模式(1)-单例模式

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

    秦子帅

扫码关注云+社区

领取腾讯云代金券