设计模式--单例模式

一. 定义
显而易见单例就是创建一个实例

二. 单例的创建方式

  • 懒汉式
  • 饿汉式
  • 静态内部类
  • 枚举
  1. 懒汉式实现:
package design.singlePattern;
public class LazySingleton {
    private static LazySingleton lazySingleton;

    private LazySingleton() {
    }

    public static LazySingleton getInstance() {
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }

    public static void main(String[] args) {
        LazySingleton lazySingleton1 = LazySingleton.getInstance();
        LazySingleton lazySingleton2 = LazySingleton.getInstance();
        //结果为true
        System.out.println(lazySingleton1 == lazySingleton2);
    }
}

上述代码执行结果为true,确实为单例,但实际多线程并发的时候并不是单例,可能就创建了多例

package design.singlePattern;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class LazySingleton {
    private static LazySingleton lazySingleton;

    private LazySingleton() {
    }

    public static LazySingleton getInstance() {
        if (lazySingleton == null) {
            try {
                Thread.sleep(1_000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }

    public static void main(String[] args) {

        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            executorService.execute(() -> {
                System.out.println(LazySingleton.getInstance());
            });
            //打印的结果为:
            //design.singlePattern.LazySingleton@3c85a950
            //design.singlePattern.LazySingleton@2918e06d
            //design.singlePattern.LazySingleton@1ecb2403
            //design.singlePattern.LazySingleton@30633415
            //design.singlePattern.LazySingleton@3c85a950
            //所以在多线程场景下,可能就不是单例了
        }
    }
}

所以需要加同步锁,避免多线程访问创建多个实例 [synchronized]
synchronized 可以加在静态方法【锁的是类对象】,成员方法【锁的当前创建的对象/this】,方法内部同步块【锁的是当前同步块的区域】,所以建议锁加载同步块上,减小锁的范围,提高对象调用效率

同时对LazySingleton对象进行volatile,主要防止即时编译或cpu指令重排,在这里可能很多人不理解为什么加volatile,创建实例对象在java代码层面不就是new LazySingleton()一句代码吗?怎么还涉及到指令重排了?
实际可以查看字节码新建一个实例对象到底做了那些步骤:

    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: new           #2                  // class design/singlePattern/LazySingleton
         8: dup
         9: invokespecial #3                  // Method "<init>":()V
        12: putfield      #4                  // Field lazySingleton:Ldesign/singlePattern/LazySingleton;
        15: return
      LineNumberTable:

字节码层面实际 给对象开辟空间对应上面字节码的【对应上述步骤5】 -> init【9】-> 赋值 【12】,多步就会涉及到指令重排,所以就要加volatile关键字防止指令重排

package design.singlePattern;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class LazySingleton {
    private volatile static LazySingleton lazySingleton;

    private LazySingleton() {
    }

    public static LazySingleton getInstance(){
        if(lazySingleton == null) {
            synchronized (LazySingleton.class) {
                if (lazySingleton == null) {
                    try {
                        Thread.sleep(1_000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lazySingleton = new LazySingleton();
                }
            }
        }
        return lazySingleton;
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            executorService.execute(() -> {
                System.out.println(LazySingleton.getInstance());
            });
            //design.singlePattern.LazySingleton@4c1ea472
            //design.singlePattern.LazySingleton@4c1ea472
            //design.singlePattern.LazySingleton@4c1ea472
            //design.singlePattern.LazySingleton@4c1ea472
            //design.singlePattern.LazySingleton@4c1ea472
        }
    }
}

从上面的结果可以看出,多线程也可以实现单例

  1. 饿汉式实现
package design.singlePattern;

public class HungrySingleton {
    private static HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton(){

    }

    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }

    public static void main(String[] args) {
        //true
        System.out.println(HungrySingleton.getInstance() == HungrySingleton.getInstance());
    }

}

由于饿汉式是类加载后就完成了初始化,jvm可以保证线程安全
类加载的流程: 类加载器加载(双亲委派机制) -> 验证(文件格式、数据等校验) -> 准备(开辟空间赋默认值,常量直接赋初始值) -> 解析(符号引用转为地址引用) -> 初始化(静态变量、静态变量赋初始值)

  1. 静态内部类实现
package design.singlePattern;
public class InnerClassSingleton {
    private InnerClassSingleton(){

    }

    private static class InnerClass{
        private static InnerClassSingleton innerClassSingleton = new InnerClassSingleton();

    }

    public static InnerClassSingleton getInstance(){
        return InnerClass.innerClassSingleton;
    }

    public static void main(String[] args) {
        // true
        System.out.println(InnerClassSingleton.getInstance() == InnerClassSingleton.getInstance());
    }
}
  1. 枚举实现
package design.singlePattern;
public enum EnumSingleton {
    singleton;

    public static void main(String[] args) {
        //true
        System.out.println(EnumSingleton.singleton == EnumSingleton.singleton);
    }
}

三. 懒汉式、饿汉式、以及静态内部类单例模式存在的问题

  1. 懒汉式:反射创建对象非单例(无法避免)
package design.singlePattern;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class LazySingleton {
    private volatile static LazySingleton lazySingleton;
    private LazySingleton() {}
    public static LazySingleton getInstance() {
        if (lazySingleton == null) {
            synchronized (LazySingleton.class) {
                if (lazySingleton == null) {
                    try {
                        Thread.sleep(1_000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    lazySingleton = new LazySingleton();
                }
            }
        }
        return lazySingleton;
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<LazySingleton> lazySingletonConstructor = LazySingleton.class.getDeclaredConstructor();
        lazySingletonConstructor.setAccessible(true);
        LazySingleton lazySingleton = lazySingletonConstructor.newInstance();
        //false
        System.out.println(lazySingleton == LazySingleton.getInstance());
    }
}
  1. 饿汉式:反射创建对象非单例
package design.singlePattern;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class HungrySingleton {
    private static HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton(){
      
    }

    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }

    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        //由于构造方法为private所以不能直接通过构造获取对象
        //HungrySingleton hungrySingleton = HungrySingleton.class.getConstructor().newInstance();
        //Class类的getDeclaredConstructor()方法,可获取到类的私有构造器(包括带有其他修饰符的构造器)
        //但在使用private的构造器时,必须设置setAccessible()为true,才可以获取并操作该Constructor对象
        Constructor<HungrySingleton> hungrySingletonConstructor = HungrySingleton.class.getDeclaredConstructor();
        hungrySingletonConstructor.setAccessible(true);
        HungrySingleton hungrySingleton = hungrySingletonConstructor.newInstance();

        // false 通过反射获取的对象非单例
        System.out.println(hungrySingleton == HungrySingleton.getInstance());
    }
}

可以通过构造方法判断是否为非单例扔调用异常避免:

package design.singlePattern;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class HungrySingleton {
    private static HungrySingleton hungrySingleton = new HungrySingleton();
    private HungrySingleton(){
        if (hungrySingleton != null) {
            throw new IllegalCallerException("私有方法不允许调用");
        }
    }

    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }

    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException {
        //由于构造方法为private所以不能直接通过构造获取对象
        //HungrySingleton hungrySingleton = HungrySingleton.class.getConstructor().newInstance();
        //Class类的getDeclaredConstructor()方法,可获取到类的私有构造器(包括带有其他修饰符的构造器)
        //但在使用private的构造器时,必须设置setAccessible()为true,才可以获取并操作该Constructor对象
        Constructor<HungrySingleton> hungrySingletonConstructor = HungrySingleton.class.getDeclaredConstructor();
        hungrySingletonConstructor.setAccessible(true);
        HungrySingleton hungrySingleton = hungrySingletonConstructor.newInstance();

        System.out.println(hungrySingleton == HungrySingleton.getInstance());
    }
}
  1. 静态内部类:反射创建对象非单例
package design.singlePattern;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class InnerClassSingleton {
    private InnerClassSingleton(){

    }

    private static class InnerClass{
        private static InnerClassSingleton innerClassSingleton = new InnerClassSingleton();
    }

    public static InnerClassSingleton getInstance(){
        return InnerClass.innerClassSingleton;
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<InnerClassSingleton> declaredConstructor = InnerClassSingleton.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance();
        //false
        System.out.println(InnerClassSingleton.getInstance() == innerClassSingleton);
    }
}

可以通过构造方法判断是否为非单例扔调用异常避免:

package design.singlePattern;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class InnerClassSingleton {
    private InnerClassSingleton(){
        if (InnerClass.innerClassSingleton != null) {
            throw new IllegalCallerException("私有方法不允许调用");
        }
    }

    private static class InnerClass{
        private static InnerClassSingleton innerClassSingleton = new InnerClassSingleton();
    }

    public static InnerClassSingleton getInstance(){
        return InnerClass.innerClassSingleton;
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<InnerClassSingleton> declaredConstructor = InnerClassSingleton.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance();
        //false
        System.out.println(InnerClassSingleton.getInstance() == innerClassSingleton);
    }
}

单例序列化再反序列化后非同一个实例对象:主要由于反序列化并不会调用构造方法,而是根据流读取生成对象
例如:饿汉模式

package design.singlePattern;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class HungrySingleton implements Serializable {
    private static HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton(){
        if (hungrySingleton != null) {
            throw new IllegalCallerException("私有方法不允许调用");
        }
    }

    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("HungrySingletonSerializable"));
        oos.writeObject(HungrySingleton.getInstance());

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("HungrySingletonSerializable"));
        HungrySingleton hungrySingleton = (HungrySingleton) ois.readObject();

        //false
        System.out.println(HungrySingleton.getInstance() == hungrySingleton);
    }
}

避免序列化后反序列化非单例,设定序列化版本号,且替换反序列化读取流数据生成对象的方式:

package design.singlePattern;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
public class HungrySingleton implements Serializable {
    **private static final long serialVersionUID = 42L;**
    private static HungrySingleton hungrySingleton = new HungrySingleton();
    private String str;
    private HungrySingleton(){
        if (hungrySingleton != null) {
            throw new IllegalCallerException("私有方法不允许调用");
        }
    }
    public static HungrySingleton getInstance(){
        return hungrySingleton;
    }

    Object readResolve() throws ObjectStreamException {
        return hungrySingleton;**
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("HungrySingletonSerializable"));
        oos.writeObject(HungrySingleton.getInstance());

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("HungrySingletonSerializable"));
        HungrySingleton hungrySingleton = (HungrySingleton) ois.readObject();

        //true
        System.out.println(HungrySingleton.getInstance() == hungrySingleton);
    }
}

总结:单例虽然是设计模式中最简单的模式,但是涉及的内容繁多,希望可以帮助到大家

posted @ 2021-03-10 16:38  神木影月  阅读(75)  评论(0)    收藏  举报