单例模式问题清单

什么是单例模式

任何时候都只有一个实例,并提供全局访问点

什么是懒汉模式

使用的时候才开始实例化

  1. 线程不安全的懒汉模式
public class LazySingleton{
        public static LazySingleton singleton;

    /**
     * 提供全局访问点
     * @return
     */
    public static LazySingleton getInstance(){
            if (singleton==null){
                /**
                 * 在多线程情况下,可能导致多个线程同时进入该if区,从而单例被破环
                 * 测试如下:
                 *  TimeUnit.MILLISECONDS.sleep(1200);
                 */
                singleton=new LazySingleton();
                //编译器,cpu,JIT会对字节码进行重排序(2,3顺序可能会改变)
                //1.分配空间-----》返回一个指向该空间的内存引用
                //2.对空间进行初始化
                //3.把内存引用赋值给demo变量
            }
            return singleton;
        }

    /**
     * 防止外界调用构造方法,破坏单例
     */
    private LazySingleton(){

        }
}

线程安全的双重检验懒汉模式【线程安全,防止指令重排,双重检查优化】
双重检验锁为何非常高效,并且在多线程的单例中应用广泛

public class LazySingleton {
    //public static LazySingleton singleton;  //不加volatile会造成(jvm对它的)重排序,引发空指针问题
    /**
    *如果Helper对象是一个不一变对象,即Helper对象的所有域(变量)都是final类型,那么即使没有使用 volatile ,双重检验锁也可以正常运行。这个思想主要是引用一个和int或者float一样的不可变对象(像String 或者 Integer),读写不可变对象的引用时都是原子的。
    */
    public volatile static LazySingleton singleton;

    /**
     * 提供全局访问点
     *
     * @return
     */
    //public static synchronized LazySingleton getInstance(){  //锁加在这里,太笨重了,性能低下
    public static LazySingleton getInstance() {
        if (singleton == null) {
            //2个线程A,B过来了
            synchronized (LazySingleton.class) {
                /**
                 * synchronized + 双重检查机制
                 * 这是很高效的
                 * 如果synchronized 加在方法上,那么将在每一次执行getInstance()方法时使用同步锁。而双重检验锁的方式可以避免在创建helper对象后依然使用同步方法:

// 错误的多线程版本
                 */
                if (singleton == null) {
                /**
                *这一段代码并不能在使用优化过的编译器或者共享内存的多处理器的情况下正确执行。(译者注:我们使用的hotspot就有指令重排序等优化,指令重排序会影响双重检验锁正常运行)
                */
                    //如果没有if判断,A来new了一下,释放了锁,B又来new了一下,释放了锁
                    singleton = new LazySingleton();
                    //编译器,cpu,JIT会对字节码进行重排序(2,3顺序可能会改变,
                    // 这样的话:来了一个C线程,开始第一个if的判断,发现singleton有引用了,【但该空间尚未初始化,就会导致null空指针】)
                    //1.分配空间-----》返回一个指向该空间的内存引用
                    //2.对空间进行初始化
                    //3.把内存引用赋值给demo变量
                }
            }
        }
        return singleton;
    }

    /**
     * 防止外界调用构造方法,破坏单例
     */
    private LazySingleton() {

    }
}

其在spring中的应用如下:

package org.springframework.core;
public class ReactiveAdapterRegistry {

	@Nullable
	private static volatile ReactiveAdapterRegistry sharedInstance;

	public static ReactiveAdapterRegistry getSharedInstance() {
		ReactiveAdapterRegistry registry = sharedInstance;
		if (registry == null) {
			synchronized (ReactiveAdapterRegistry.class) {
				registry = sharedInstance;
				if (registry == null) {
					registry = new ReactiveAdapterRegistry();
					sharedInstance = registry;
				}
			}
		}
		return registry;
	}

饿汉模式

在类加载阶段就完成了实例的初始化

类加载
加载:—加载对应的二进制文件,并且在方法区创建对应的数据结构
连接:验证【验证字节码文件有无被篡改,是否合乎语法】,准备【引用类型赋值null,int赋值0.boolean赋值true】,解析【符号引用转直接引用】
初始化:给静态属性赋值

public class HungrySingeton {
    //类加载:class先创建,才能加载实例
    private static HungrySingeton instance=new HungrySingeton();   //类加载加了锁的synchronized,多线程同时类加载,也只会有一个线程能够完成类加载

    public static HungrySingeton getInstance(){
        return instance;
    }
    private HungrySingeton(){}
}

延迟加载的饿汉模式

public class InnerClassSingleton {

    //这是一种懒加载
    static class InnerClass {
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }

    public static InnerClassSingleton getInstance() {
        //InnerClassSingleton的类加载是导致InnerClass也类加载,此时会给静态属性赋初值。
        //执行InnerClass.instance是会触发InnerClassSingleton的类加载
        return InnerClass.instance;  //使用内部类达到延迟类加载的效果。
    }

    //私有的构造方法:不允许外部进行实例化
    private InnerClassSingleton() {
        //使用异常来防止反射的方式破坏单例
        if (InnerClass.instance!=null){
            throw new RuntimeException("单例类,不允许多个实例");
        }
    }
}

反射对内部类实现的单例破坏如下:

public class Anti {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<InnerClassSingleton>declaredConstructor=InnerClassSingleton.class.getDeclaredConstructor();
        /**
         * 反射破坏
         */
        declaredConstructor.setAccessible(true);
        InnerClassSingleton innerClassSingleton=declaredConstructor.newInstance();  //反射获得的实例
        InnerClassSingleton instance=innerClassSingleton.getInstance();     //通过静态方法获得的实例
        System.out.println(innerClassSingleton==instance);  //结果为false---说明反射破坏了单例
    }
}

枚举实现单例

枚举类型其实是语法糖

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {
	 private final String name;   //名称
	 private final int ordinal;   //索引
	protected Enum(String name, int ordinal) {
	        this.name = name;
	        this.ordinal = ordinal;
	    }
}
final class EnumSingleton extends Enum<EnumSingleton>{
   EnumSingleton INSTANCE;
   EnumSingleton[] $values;
   public static valueOf();
   static{
      new EnumSingleton (INSTACE,);
      //给静态属性赋值
//也是借助jvm类加载机制来保证单例
   }
    
}

枚举类型不支持反射来创建。

 public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
              //枚举类型不支持反射来创建,枚举天然支持反射的攻击
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }

最后枚举的单例的代码实现

public enum  EnumSingleton {
    INSTANCE;
    public void print(){
        System.out.println(this.hashCode());
    }
}

序列化实现饿汉模式

public class SerializableSingeton implements Serializable {

    // jvm会根据class的元素默认生成一个序列化id,所以最好加上

    // private final long serialVersin=42L;

    private static SerializableSingeton instance = new SerializableSingeton();   //类加载加了锁的synchronized,多线程同时类加载,也只会有一个线程能够完成类加载

    public static SerializableSingeton getInstance() {
        return instance;
    }

    String name;  //最好指定序列化id,提高兼容性,像这里不加的话,加上了name属性,反序列化后就会报错

    private SerializableSingeton() {
    }

    Object readResolve() throws ObjectStreamException{
      return instance;  //反序列化的时候就不会从流中读取,--指定签名,它会直接从 private static SerializableSingeton instance = new SerializableSingeton();  中拿
    }
}
public class Demo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //Demo demo=new Demo(); //new的后面回去查看是否完成了Demo.class文件的加载,初始化。如果没完成,就需要去完成
        //在实例化的时候需要先完成类的加载。类加载完成后才会对实例进行初始化
        SerializableSingeton instance=SerializableSingeton.getInstance();

        /**
         * 序列化
         */
        ObjectOutputStream objectOutputStream=new ObjectOutputStream(new FileOutputStream("instance"));
        objectOutputStream.writeObject(instance);
        objectOutputStream.close();

        /**
         * 反序列化
         */
        ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream("instance"));
        Object object=objectInputStream.readObject();
        SerializableSingeton singeton= (SerializableSingeton) object;
        System.out.println(singeton==instance);  //返回false,序列化也可能会破坏单例。
        //从指定地方获取实例,而非从流中获取实例。就是保证签名的一致性
    }
}

ObjectInputStream里面的readObject方法:
在这里插入图片描述

javap反汇编工具
在这里插入图片描述
javap -v -p Demo.class

public class Demo {
    public static void main(String[] args) {
        Demo demo=new Demo();
        //1.分配空间-----》指向该空间的内存引用
        //2.在操作数栈上复制一个引用在下一个指令上面
        //3.指令会消耗一个对应的引用,初始化
        //4.把内存引用赋值给demo
    }
}
Classfile /E:/WORK_SPACE/Idea_workspace/blog_program_record/target/classes/org/lmj/singleton/Demo.class
  Last modified 2020-1-5; size 425 bytes
  MD5 checksum c6dd301a55168daa29e599d7ad28b5a2
  Compiled from "Demo.java"
public class org.lmj.singleton.Demo
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #4.#19         // java/lang/Object."<init>":()V
   #2 = Class              #20            // org/lmj/singleton/Demo
   #3 = Methodref          #2.#19         // org/lmj/singleton/Demo."<init>":()V
   #4 = Class              #21            // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Utf8               LineNumberTable
   #9 = Utf8               LocalVariableTable
  #10 = Utf8               this
  #11 = Utf8               Lorg/lmj/singleton/Demo;
  #12 = Utf8               main
  #13 = Utf8               ([Ljava/lang/String;)V
  #14 = Utf8               args
  #15 = Utf8               [Ljava/lang/String;
  #16 = Utf8               demo
  #17 = Utf8               SourceFile
  #18 = Utf8               Demo.java
  #19 = NameAndType        #5:#6          // "<init>":()V
  #20 = Utf8               org/lmj/singleton/Demo
  #21 = Utf8               java/lang/Object
{
  public org.lmj.singleton.Demo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lorg/lmj/singleton/Demo;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class org/lmj/singleton/Demo
         3: dup
         4: invokespecial #3                  // Method "<init>":()V
         7: astore_1
         8: return
      LineNumberTable:
        line 5: 0
        line 6: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
            8       1     1  demo   Lorg/lmj/singleton/Demo;
}
SourceFile: "Demo.java"
发布了437 篇原创文章 · 获赞 82 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/qq_41063141/article/details/103843384
今日推荐