单例模式详解(代码片段)

yuan116 yuan116     2022-12-08     318

关键词:

单例模式详解

一、单例模式分类

单例模式按照加载时间可以分为两种:

  • 懒汉式
  • 饿汉式

二、各种单例模式详解

2.1 饿汉式
public class Singleton 
    private static Singleton singleton = new Singleton();

    private Singleton() 
    
    
    public static Singleton getSingleton() 
        return singleton;
    

? 饿汉式单例模式在程序运行时,对象就会被创建。每次调用getSingleton方法时,都会返回同一个对象。饿汉式单例不存在线程安全问题。

2.2 懒汉式
public class Singleton implements Serializable
    private static Singleton singleton;

    private Singleton() 
    

    public static Singleton getSingleton() 
        if (null == singleton) 
            singleton = new Singleton();
            return singleton;
        
        return singleton;
    

? 在单线程中,该单例模式没有什么问题,但是在多线程程序中,该单例模式就存在线程安全问题。当第一次有多个线程调用getSingleton时,可能会产生多个singleton对象。为了解决该问题最直观的办法就是在getSingleton加锁,使在多线程情况下该方法串行化执行,保证线程安全。代码如下:

public class Singleton 
    private static Singleton singleton;

    private Singleton() 
    

    public static synchronized Singleton getSingleton() 
        if (null == singleton) 
            singleton = new Singleton();
            return singleton;
        
        return singleton;
    

? 该方法可以解决线程安全问题,但是由于初始化对象和每次获取对象都要通过锁,导致性能大大下降。而实际我们只是希望在初次初始化对象时保证线程安全,在之后获取对象不存在线程安全问题,所以就产生了双重检查锁。代码如下:

public class Singleton implements Serializable 
    private static Singleton singleton;

    private Singleton() 
    

    public static Singleton getSingleton() 
        if (null == singleton) 
            synchronized (Singleton.class) 
                if (null == singleton) 
                    singleton = new Singleton();
                    return singleton;
                
            
        
        return singleton;
    

? 但是仔细分析,该方法还是有问题,因为new Singleton()这个初始化对象可以分为三个操作,1、分配内存空间;2、初始化对象;3、设置实例化对象指向分配的地址。在一些JIT编译器中,为了达到优化目的,可能会对这三个操作进行重排序。当2和3顺序颠倒后,就可能发生线程安全问题,原因是当初始化对象后,但是对象还没有指向分配的内存地址,而此时有另一个线程运行到if(null==singleton)这行代码,就会判断为false,然后就会去获取对象,从而发生错误。虽然这种几率很小,并且并不是所有的编译器都会对上述操作进行重排序,但是该代码始终是存在问题的代码,所以就需要去解决。

重排序:重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。 --《java并发编程的艺术》

? 通过对该种情况分析,有两种解决思路:

  1. 禁止重排序
  2. 在实例化操作完成前,不允许其他线程看到该对象实例化的具体过程(也就是在实例化完成前,其他线程读取的情况会一直为null)。

具体实现:

第一种情况我们理所当然会想到volatile关键字,因为它可以禁止指令重排序。具体实现代码如下:

public class Singleton 
	private volatile static Singleton singleton;

	private Singleton() 
	

	public  static Singleton getSingleton()
		if (null == singleton) 
			synchronized (Singleton.class) 
				if (null == singleton) 
                    singleton = new Singleton();
                    return singleton;
				
			
		
		return singleton;
	

第二种方案不容易想到具体代码实现,《java并发编程的艺术》中是指可以通过静态工厂实例化对象来实现(章节:3.8.3),具体实现代码:

public class Singleton 
  private volatile static Singleton singleton;

  public Singleton() 
  

public class SingletonFactory 
	private static class SingletonHolder 
		public static Singleton singleton = new Singleton();
	
	public static Singleton getSingleton() 
		return SingletonHolder.singleton;
	

? 该种方法主要是基于JVM在类的初始化阶段(即在Class被加载后,且被线程使用之前),会执行类的初始化。在执行类的初始化期间,JVM会去获取一个锁,这个锁可以同步多个线程对同一个类的初始化。这种方法其实和饿汉式类似,只是通过静态工厂来实现再需要该对象时再进行加载。但是上面的代码并完善,因为是由两个类来实现单例,单例对象的构造方法也是公共的(能够new出新的对象)。因此最好将单例实现放在一个类中,并能达到懒加载的效果,代码如下:

public class Singleton 
	private static class SingletonHolder 
		private static final Singleton INSTANCE = new Singleton();
	

	private Singleton () 
	public static Singleton getSingleton() 
		return SingletonHolder.INSTANCE;
	

2.3 枚举实现单例

? 严格的来说,枚举也是懒汉式单例,因为只有在枚举类被调用后才会实例化该单例。具体代码如下:

public enum Singleton 
    INSTANCE;

    public void doEverything() 
    

枚举类实现的单例不仅是线程安全的,而且不会被序列化和反射破坏,这是其他形式的单例不能达到的,这也是枚举实现单例备受推崇的原因。

三、破坏单例模式的各种情况

? 单例能够减少重复创建对象,提高性能。但是单例有时是能够被破坏的,下面我们来了解单例可能被破坏的情况。

3.1 反射破坏单例
public class Singleton 
    private static Singleton singleton = new Singleton();

    private Singleton() 
    

    public static Singleton getSingleton() 
        return singleton;
    


public class DestroySingleton 
    public static void main(String[] args) throws Exception 
        Class clazz = Class.forName("com.yuan.Singleton");
        Constructor con = clazz.getDeclaredConstructor();
        con.setAccessible(true);
        Singleton singleton1 = (Singleton) con.newInstance();
        Singleton singleton2 = Singleton.getSingleton();
        System.out.println(singleton1 == singleton2);
    


输出结果:false

? 结果表明,通过反射将会实例化一个新的对象,从而破坏单例模式。但是当采用枚举实现单例时,由于枚举类实现反射会报错,从而可以避免单例破坏。

3.2 序列化破坏单例
public class Singleton implements Serializable
    private static Singleton singleton = new Singleton();

    private Singleton() 
    

    public static Singleton getSingleton() 
        return singleton;
    


public class DestroySingleton 
    public static void main(String[] args) throws Exception 
        //write Object
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\file.txt"));
        oos.writeObject(Singleton.getSingleton());
        //read Object
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\file.txt"));
        Singleton singleton = (Singleton) ois.readObject();
        System.out.println(singleton == Singleton.getSingleton());
    


输出结果:false

? 通过代码知道,序列化会实例化一个新对象。但是当单例对象添加readResolve方法时,可以避免单例被破坏

立即加载:

public class Singleton implements Serializable
    private static Singleton singleton = new Singleton();

    private Singleton() 
    

    public static Singleton getSingleton() 
        return singleton;
    

    /**
     * 添加该方法可以避免单例被破坏
     */
    public Object readResolve() 
        return Singleton.getSingleton();
    


public class DestroySingleton 
    public static void main(String[] args) throws Exception 
        //write Object
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\file.txt"));
        oos.writeObject(Singleton.getSingleton());
        //read Object
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\file.txt"));
        Singleton singleton = (Singleton) ois.readObject();
        System.out.println(singleton == Singleton.getSingleton());
    


输出结果:true

懒加载:

public class Singleton implements Serializable
    private volatile static Singleton singleton;

    private Singleton() 
    

    public static Singleton getSingleton() 
        if (null == singleton) 
            synchronized (Singleton.class) 
                if (null == singleton) 
                    singleton = new Singleton();
                    return singleton;
                
            
        
        return singleton;
    

    /**
     * 添加该方法可以避免单例被破坏
     */
    public Object readResolve() 
        return Singleton.getSingleton();
    


public class DestroySingleton 
    public static void main(String[] args) throws Exception 
        //write Object
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("E:\file.txt"));
        oos.writeObject(Singleton.getSingleton());
        //read Object
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("E:\file.txt"));
        Singleton singleton = (Singleton) ois.readObject();
        System.out.println(singleton);
        System.out.println(Singleton.getSingleton());
        System.out.println(singleton == Singleton.getSingleton());
    

? 其实就是java约定,在实例化后会再判断该实例是否有该方法,如果有该方法则会调用该方法将返回结果返回。这里要注意的是立即加载模式下readResolve方法可以直接返回singleton这个类变量,但是在懒加载中这种做法不建议,因为如果程序启动没有获取过对像实例而是直接通过序列化来获得,就会返回一个空值。

四、总结

? 单例实现的方式有很多,有线程安全也有不安全的,所以需要根据实际业务具体分析,再决定采用。主要考虑的并发、反射以及序列化。

单例模式详解(代码片段)

文章目录前言一、单例模式的特点?二、饿汉式单例模式三、懒汉式单例模式1.懒汉模式的创建2.懒汉模式的线程不安全问题及解决前言一、单例模式的特点?单例模式是一种常见的设计模式,它有以下特点:单例... 查看详情

单例模式详解(代码片段)

单例模式的几种实现方式单例模式的实现有多种方式,如下所示:1、懒汉式,线程不安全是否Lazy初始化:是是否多线程安全:否实现难度:易描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为... 查看详情

java设计模式(6:单例模式详解)(代码片段)

单例模式是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局的访问点。在java语言当中,有着两种方式构建单例模式:饿汉式单例和懒汉式单例。单例模式作为一种创建型模式,在日常开发中用处极广,我们先... 查看详情

多线程下的单例模式详解(代码片段)

1.单例模式(1)单例模式简介(2)实现方式①饿汉式②懒汉式2.多线程下的单例模式(1)Synchronized(2)双重检查锁(3)双重检查锁+Volatile补充知识点1.单例模式(1)单例模式简介单例模式的作用单例模式是为了一个类的示例只有一个,并... 查看详情

14-java的单例设计模式详解(代码片段)

预加载模式代码:publicclassSingletonprivatevolatilestaticSingletonsingleton=newSingleton();privateSingleton()publicstaticSingletongetInstance()returnsingleton;分析:这种方式是预先加载单例,适合于单例对象初始 查看详情

[转]单例模式详解(代码片段)

3.1单例模式的动机      对于一个软件系统的某些类而言,我们无须创建多个实例。举个大家都熟知的例子——Windows任务管理器,如图3-1所示,我们可以做一个这样的尝试,在Windows的“任务栏”的右键弹出... 查看详情

设计模式之单例模式详解(java)(代码片段)

目录一、单例模式1.1饿汉式1.2懒汉式1.3DCL懒汉式(双重检验锁)1.4静态内部类1.5枚举一、单例模式单例模式的实现方式有许多种,重点是以下五种:饿汉式、懒汉式、双重校验锁(DCL懒汉式)、静态内部类... 查看详情

设计模式之单例模式详解(java)(代码片段)

目录一、单例模式1.1饿汉式1.2懒汉式1.3DCL懒汉式(双重检验锁)1.4静态内部类1.5枚举一、单例模式单例模式的实现方式有许多种,重点是以下五种:饿汉式、懒汉式、双重校验锁(DCL懒汉式)、静态内部类... 查看详情

架构师内功心法,经典高频面试的单例模式详解(代码片段)

一、单例模式应用场景单例模式(SinglePattern)是指确保一个类在任何情况下绝对只是一个实例,并提供一个全局的访问点。 单例模式在现实生活中的应用也很广泛。例如国家总统、公司CEO、部门经理等。在java标准中,Servlet... 查看详情

详解单例模式(代码片段)

文章目录单例模式1.饿汉式单例2.懒汉式单例3.静态内部类获取单例4.反射破环单例模式正常获取破环一:一个采用正常方式获取单例,一个采用反射的方式获取单例破环二,不使用类中提供的引用创建对象,每次创建对象都使用反射的... 查看详情

详解单例模式(代码片段)

文章目录单例模式1.饿汉式单例2.懒汉式单例3.静态内部类获取单例4.反射破环单例模式正常获取破环一:一个采用正常方式获取单例,一个采用反射的方式获取单例破环二,不使用类中提供的引用创建对象,每次创建对象都使用反射的... 查看详情

详解单例模式(代码片段)

关于单例模式,话不多说,即程序运行时无论New了多少次,即内存中只有一个实例对象。即对象的HasHCode一致。单例模式的两大类1、饿汉模式(即加载时就创建对象)  -1、直接实例化饿汉模式  -2、静态代码块饿汉模式(... 查看详情

设计模式之单例模式详解和应用(代码片段)

目录1单例模式的应用场景介绍2饿汉式单例模式2.1静态方法获得私有成员对象2.2利用静态代码块与类同时加载的特性生成单例对象2.3优缺点3懒汉式单例模式2.1加锁2.2双重检查锁2.3静态内部类4反射破坏单例5序列化破坏单例(... 查看详情

java设计模式图文代码案例详解java五大创建者模式建造者原型(抽象)工厂单例模式(代码片段)

...结四、原型模式1、介绍2、实例3、实例拓展4、总结五、单例模式1、介绍2、实例 查看详情

设计模式——单例模式(代码片段)

在Java开发过程中,很多场景下都会碰到或要用到单例模式,在设计模式里也是经常作为指导学习的热门模式之一,相信每位开发同事都用到过。我们总是沿着前辈的足迹去做设定好的思路,往往没去探究为何这么做,所以这篇... 查看详情

详解单例模式及其在sping中的最优实践(代码片段)

(一)什么是单例模式在程序中,每new()一个对象,就会有一个对象实例生成。有时候在程序中,需要有一个在完整运行状态下只需要生成一个的实例,我们把这种实例称为单例。抽象到设计模式中,... 查看详情

详解单例模式及其在sping中的最优实践(代码片段)

(一)什么是单例模式在程序中,每new()一个对象,就会有一个对象实例生成。有时候在程序中,需要有一个在完整运行状态下只需要生成一个的实例,我们把这种实例称为单例。抽象到设计模式中,... 查看详情

学习笔记spring中常用的设计模式(代码片段)

...厂模式1.1简单工厂模式1.2工厂方法模式1.3抽象工厂模式2.单例模式2.1饿汉模式2.2懒汉模式2.3破坏单例的方式2.4注册式单例模式2.4.1枚举单例模式2.4.2容器式单例模式3.原型模式详解3.1浅克隆3.2深克隆4.代理模式4.1为什么要用代理模... 查看详情