首页 > 其他分享 >23种设计模式之单例模式

23种设计模式之单例模式

时间:2023-08-29 18:33:51浏览次数:58  
标签:Singleton 23 singleton private instance 单例 设计模式 public

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

如何实现一个单例:

常见的单例设计模式,有如下五种写法,在编写单例代码的时候要注意以下几点:

1、构造器需要私有化

2、暴露一个公共的获取单例对象的接口

3、是否支持懒加载(延迟加载)

4、是否线程安全

1、饿汉式

饿汉式的实现方式比较简单。在类加载的时候,instance 静态实例就已经创建并初始化好了,所以,instance 实例的创建过程是线程安全的。从名字中我们也可以看出这一点。具体的代码实现如下所示:

public class EagerSingleton {  
    private static Singleton instance = new Singleton();  
    private Singleton (){}  
    public static Singleton getInstance() {  
    	return instance;  
    }  
}

大多数文章觉得饿汉式不能支持懒加载,即使不使用也会浪费资源,一方面是内存资源,一方面会增加初始化的开销。

1、现代计算机不缺这一个对象的内存。

2、如果一个实例初始化的过程复杂那更加应该放在启动时处理,避免卡顿或者构造问题发生在运行时。满足 fail-fast 的设计原则。

2、懒汉式

有饿汉式,对应地,就有懒汉式。懒汉式相对于饿汉式的优势是支持延迟加载,具体的代码实现如下所示:

public class LazySingleton {  
    private static Singleton instance;  
    private Singleton (){}  

    public static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

以上的写法本质上是有问题,当面对大量并发请求时,其实是无法保证其单例的特点的,很有可能会有超过一个线程同时执行了new Singleton();

解决方案:

public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  

    public synchronized static Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

以上的写法可以保证jvm中有且仅有一个单例实例存在,但是方法上加锁会极大的降低获取单例对象的并发度。同一时间只有一个线程可以获取单例对象,为了解决以上的方案则有了第三种写法。

3、双重检查锁

饿汉式不支持延迟加载,懒汉式有性能问题,不支持高并发。那我们再来看一种既支持延迟加载、又支持高并发的单例实现方式,也就是双重检测实现方式:

在这种实现方式中,只要 instance 被创建之后,即便再调用 getInstance() 函数也不会再进入到加锁逻辑中了。所以,这种实现方式解决了懒汉式并发度低的问题。具体的代码实现如下所示:

public class DclSingleton {  
    // volatile如果不加可能会出现半初始化的对象
    // 现在用的高版本的 Java 已经在 JDK 内部实现中解决了这个问题(解决的方法很简单,只要把对象 new 操作和初始化操作设计为原子操作,就自然能禁止重排序),为了兼容性我们加上
    private volatile static Singleton singleton;  
    private Singleton (){}  

    public static Singleton getInstance() {  
        if (singleton == null) {  
            synchronized (Singleton.class) {  
                if (singleton == null) {  
                    singleton = new Singleton();  
                }  
            }  
        }  
        return singleton;  
    }  
}

4、静态内部类

我们再来看一种比双重检测更加简单的实现方法,那就是利用 Java 的静态内部类。它有点类似饿汉式,但又能做到了延迟加载。具体是怎么做到的呢?我们先来看它的代码实现。

public class InnerSingleton {

    /** 私有化构造器 */
    private Singleton() {
    }

    /** 对外提供公共的访问方法 */
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }

    /** 写一个静态内部类,里面实例化外部类 */
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

}

SingletonHolder 是一个静态内部类,当外部类 Singleton被加载的时候,并不会创建 SingletonHolder 实例对象。只有当调用 getInstance() 方法时,SingletonHolder 才会被加载,这个时候才会创建 instance。insance 的唯一性、创建过程的线程安全性,都由 JVM 来保证。所以,这种实现方法既保证了线程安全,又能做到延迟加载。

5、枚举

最后,我们介绍一种最简单的实现方式,基于枚举类型的单例实现。这种实现方式通过 Java 枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。具体的代码如下所示:

这是一个最简单的实现,因为枚举类中,每一个枚举项本身就是一个单例的:

public enum EnumSingleton {
    INSTANCE;
}

更通用的写法如下:

public class EnumSingleton {
    private Singleton(){
    }   
    public static enum SingletonEnum {
        EnumSingleton;
        private EnumSingleton instance = null;
        private SingletonEnum(){
            instance = new Singleton();
        }
        public EnumSingleton getInstance(){
            return instance;
        }
    }
}

还可以将单例项作为枚举的成员变量:

public enum GlobalCounter {
    INSTANCE;
    private AtomicLong atomicLong = new AtomicLong(0);

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

这种写法是Head-first中推荐的写法,他除了可以和其他的方式一样实现单例,他还能有效的防止反射入侵。

6、反射入侵

想要阻止其他人构造实例仅仅私有化构造器还是不够的,因为还可以使用反射获取私有构造器进行构造,当然使用枚举的方式是可以解决这个问题的,对于其他的书写方案,通过下边的方式解决:

public class Singleton {
    private volatile static Singleton singleton;
    private Singleton (){
        if(singleton != null) 
            throw new RuntimeException("实例:【"
                    + this.getClass().getName() + "】已经存在,该实例只允许实例化一次");
    }

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

此时方法如下:

@Test
public void testReflect() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
    Class<DclSingleton> clazz = DclSingleton.class;
    Constructor<DclSingleton> constructor = clazz.getDeclaredConstructor();
    constructor.setAccessible(true);

    boolean flag = DclSingleton.getInstance() == constructor.newInstance();

    log.info("flag -> {}",flag);
}

结果如下:

23种设计模式之单例模式_线程安全

#7、序列化与反序列化安全

到止的单例依然是有漏洞的,如下代码:

@Test
public void testSerialize() throws IllegalAccessException, NoSuchMethodException, IOException, ClassNotFoundException {
    // 获取单例并序列化
    Singleton singleton = Singleton.getInstance();
    FileOutputStream fout = new FileOutputStream("D://singleton.txt");
    ObjectOutputStream out = new ObjectOutputStream(fout);
    out.writeObject(singleton);
    // 将实例反序列化出来
    FileInputStream fin = new FileInputStream("D://singleton.txt");
    ObjectInputStream in = new ObjectInputStream(fin);
    Object o = in.readObject();
    log.info("他们是同一个实例吗?{}",o == singleton);
}

我们废了九牛二虎之力还是没能阻止他返回false,结果如下:

23种设计模式之单例模式_饿汉式_02

readResolve()方法可以用于替换从流中读取的对象,在进行反序列化时,会尝试执行readResolve方法,并将返回值作为反序列化的结果,而不会克隆一个新的实例,保证jvm中仅仅有一个实例存在:

public class Singleton implements Serializable {
    
    // 省略其他的内容
    public static Singleton getInstance() {
        
    }
    
    // 需要加这么一个方法
    public Object readResolve(){
        return singleton;
    }
}

23种设计模式之单例模式_线程安全_03


标签:Singleton,23,singleton,private,instance,单例,设计模式,public
From: https://blog.51cto.com/u_10956218/7279130

相关文章

  • Jeecg-Boot存在前台SQL注入漏洞CVE-2023-1454
    Jeecg-boot简介jeecgBoot是一款基于BPM的低代码平台!前后端分离架构SpringBoot2.x,SpringCloud,AntDesign&Vue,Mybatis-plus,Shiro,JWT,支持微服务。强大的代码生成器让前后端代码一键生成,实现低代码开发!JeecgBoot引领新低代码开发模式OnlineCoding->代码生成器->手工MERGE,帮助J......
  • CCF HPC China2023|澎峰科技:使能先进计算,赋能行业应用
    CCFHPCChina2023圆满落幕! 桂秋八月,为期三天的中国高性能计算领域最高规格盛会——2023CCF全球高性能计算学术年会(HPCChina)在青岛红岛国际展览中心圆满落幕。行业超算大咖、顶级学界精英、先锋企业领袖参会者齐聚山东青岛,共同探讨高性能计算、人工领域、大数据等诸多前沿领域......
  • 2023.08.29T3 - summer - solution
    summerProblemSolution挺好的题,题解也写得很清楚,因此我不过是把题解抄一遍。赛时打了\(40\)分,然后挂了\(20\)分,因为不会前缀和(这个人暴力求区间和,铸币吧)。前\(40\)分就是记忆化搜索+单调栈:首先考察对于一个确定的序列,如何求出一段区间的权值和。那么首先就要知道如......
  • 20230627 java.net.URL
    介绍java.net.URLpublicfinalclassURLimplementsjava.io.SerializableURI是个纯粹的语法结构,包含用来指定Web资源的字符串的各种组成部分URL是URI的一个特例,它包含了用于定位Web资源的足够信息URL语法authority部分具有以下形式:[user-info@]host[:port]......
  • 20230627 java.net.URI
    介绍java.net.URIpublicfinalclassURIimplementsComparable,SerializableURI是个纯粹的语法结构,包含用来指定Web资源的字符串的各种组成部分URL是URI的一个特例,它包含了用于定位Web资源的足够信息URI语法URI具有以下句法:[scheme:]schemeSpecficPart[#fra......
  • 20230627 java.net.Socket
    介绍java.net.SocketpublicclassSocketimplementsjava.io.Closeable套接字(Socket)是网络软件中的一个抽象概念,负责启动该程序内部和外部之间的通信API构造器Socket()Socket(Proxyproxy)Socket(Stringhost,intport)throwsUnknownHostException,IOException......
  • 20230627 java.net.ServerSocket
    介绍java.net.ServerSocketpublicclassServerSocketimplementsjava.io.Closeable服务器套接字ServerSocket类用于建立套接字,accept用于告诉程序不停地等待,直到有客户端连接到这个端口。一旦有人通过网络发送了正确的连接请求,并以此连接到了端口上,该方法就会返回一个表......
  • 20230627 java.net.InetSocketAddress
    介绍java.net.InetSocketAddresspublicclassInetSocketAddressextendsSocketAddressAPI构造器InetSocketAddress(intport)InetSocketAddress(InetAddressaddr,intport)InetSocketAddress(Stringhostname,intport)publiccreateUnresolved创建未解析的I......
  • 20230627 java.net.InetAddress
    介绍java.net.InetAddresspublicclassInetAddressimplementsjava.io.Serializable因特网地址,是一串数字表示的主机地址(IPv4是4字节,IPv6是16字节)支持在主机名和因特网地址之间进行转换封装了一个字节序列(IPv4是4字节),byte的取值范围是[-126,125),IPv4的大小......
  • 20230627 java.nio.channels.SocketChannel
    介绍java.nio.channels.SocketChannelpublicabstractclassSocketChannelextendsAbstractSelectableChannelimplementsByteChannel,ScatteringByteChannel,GatheringByteChannel,NetworkChannelAPIopen打开一个套接字通道,并将其连接到远程地址bindconne......