1. 懒汉模式

    /**
     * @Description 懒汉模式 实现了延迟加载,synchronized会额外消耗资源,因此执行效率较低
     */
    public class LazySingleton {
    
     private static LazySingleton lazySingleton;
    
     private LazySingleton(){
    
     }
    
     public synchronized static LazySingleton getLazySingleton(){
         if(lazySingleton == null){
             lazySingleton = new LazySingleton();
         }
         return lazySingleton;
     }
    
     public static void main(String[] args) {
         LazySingleton h1 = LazySingleton.getLazySingleton();
         LazySingleton h2 = LazySingleton.getLazySingleton();
         System.out.println(h1==h2);
     }
    }
  2. 饿汉模式

    1
    /**
     * @Description  饿汉模式
     * 类加载时完成对象实例化,避免多线程同步问题,但是无论是否使用对象,都在占用内存空间
     */
    public class HungrySingleton {
    
     private static HungrySingleton singleton = new HungrySingleton();
    
     private HungrySingleton(){
     }
    
     public static HungrySingleton getSingleton(){
         return singleton;
     }
    
     public static void main(String[] args) {
         HungrySingleton h1 = HungrySingleton.getSingleton();
         HungrySingleton h2 = HungrySingleton.getSingleton();
         System.out.println(h1==h2);
     }
    }
  3. 双重校验锁模式

  4. @description: 1.懒加载,2 volatile 保证了指令不会重排
    */

    public class DoubleCheckSingleton {
    
    private volatile static DoubleCheckSingleton singleton;
    
    private DoubleCheckSingleton() {
    }
    
     public static DoubleCheckSingleton getInstance(){
         if(singleton == null){
             synchronized (DoubleCheckSingleton.class){
                 if(singleton == null){
                     singleton = new DoubleCheckSingleton();
                 }
             }
         }
         return singleton;
     }
    
     public static void main(String[] args) {
        DoubleCheckSingleton d1 = DoubleCheckSingleton.getInstance();
        DoubleCheckSingleton d2 = DoubleCheckSingleton.getInstance();
         System.out.println(d1==d2);
     }
    }

    singleton = new DoubleCheckSingleton();实际过程分为3步
    1.为singleton分配内存空间
    2.初始化singleton
    3.singleton指向分配的内存地址
    由于jvm具有指令重排的特性,因此指令可能变成1->3->2。指令重排在单线程下不会出现问题,但多线程环境中会造成一个线程获得没有初始化的实例
    使用volatile可以禁止指令重排,在多线程环境下也能正常运行

静态内部类实现单例模式
1.静态内部类实现单例模式,既保证了延迟加载,又能保证线程安全

public class Singleton {

    private Singleton() {
        System.out.println("Singleton初始化方法");
    }
    static {
        System.out.println("Singleton的静态块");
    }
    
    public static Singleton getInstance(){
        return SingletonInner.INSTANCE;
    }
    
    private static class SingletonInner{
        static {
            System.out.println("静态内部类.....");
        }
        private static final Singleton INSTANCE = new Singleton();
    }

    // 重新readResolve,解决反序列化创建新对象问题
     private Object readResolve() {
        return SingletonInner.INSTANCE;
    }
    
    public static void main(String[] args) {
        //创建外部类对象,不会自动加载静态内部类
        Singleton singleton = new Singleton();
        //访问内部类对象时或者访问内部类对象的静态属性时,才会去初始化类,这样保证了延迟加载
        Singleton.SingletonInner inner = new SingletonInner();
    }
}

静态内部类实现单例模式也不是完全安全的;反射,反序列化会破坏它的单例。当把对象序列化后,再次反序列化时属于深拷贝(完全复制一个对象,但是会在内存中申请新的空间)

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Singleton singleton = Singleton.getInstance();
        System.out.println(singleton.hashCode());
        //将单例对象序列化
        FileOutputStream fos = new FileOutputStream("D://test.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(singleton);
        //反序列化生成一个新对象,两次hashcode值不一样
        FileInputStream fis = new FileInputStream("D://test.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Singleton result = (Singleton) ois.readObject();
        System.out.println(result.hashCode());
    }

1) 从类的加载分析为什么使用静态内部类出现延迟加载
类加载的生命周期分为7个阶段: 加载,验证,准备,解析,初始化,使用,卸载
image.png

  • 加载: 在这个阶段虚拟机需要完成三件事
    (1) 通过一个类的全限定名来获取定义此类的二进制字节流
    (2) 将这个字节流中的静态储存结构转化为方法区的运行时数据结构
    (3) 在内存中生成一个java.lang.Class对象,作为方法区这个类的各种数据的访问入口
  • 验证: 这个阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
  • 准备: 为静态变量分配内存空间并设置初始值,这些变量使用的内存都在方法区中分配。int类型的变量默认值为0,布尔类型的默认值为false,非基本类型的初始值为null
  • 解析: 虚拟机将常量池的符号引用替换为直接引用
  • 初始化: 执行类的clinit()方法(clinit = class + initialize 意为类的初始化)包括为类的静态变量赋初始值和执行静态代码块中的内容,执行的先后顺序取决于在源文中出现的位置

要执行一个类的方法,需要先完成该类的初始化。 从类的生命周期中看,静态代码块的执行在类初始化阶段,main方法的执行在使用阶段。因此上面会先执行静态块中的代码

对于初始化阶段,虚拟机严格规定: 有且只有5种情况必须对类立即进行初始化
(1) 遇到new,getstatic,putstatic或invokestatic这4条字节码指令时,如果没有对类初始化,则先触发类的初始化。这4个指令对应的java场景是: 使用new创建一个新对象、访问或设置一个类的静态属性、访问类的静态方法
(2) 使用java.lang.reflect包的方法对类进行反射调用时,如果类没有初始化,则先触发其初始化
(3) 当初始化一个类时,如果发现其父类还没有初始化,则先触发其父类的初始化
(4) 当虚拟机启动时,用户需要指定一个执行的主类(包含main方法的类),虚拟机会先初始化启动类
(5) 当使用JDK 1.7 的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化

静态内部类实现单例,触发第一种情况的getstatic方法,即访问该类的静态变量,这个时候触发Inner类的初始化.

2) 线程安全
虚拟机会保证一个类的clinit()方法在多线程环境中被正确加锁,同步。如果多线程同时去初始化一个类,那么只有一个线程去执行这个类的clinit()方法,其他线程都需要阻塞等待,直到活动线程执行clinit()方法完毕。如果一个类的clinit()方法中有很耗时的操作,可能会造成多个线程长期阻塞
参考:https://www.jianshu.com/p/ef263cf7f7f9

2.枚举实现单例模式
单实例枚举实现单例模式保证了线程安全,不会被反射,反序列化破坏单例模式

public enum EnumDemo {
    INSTANCE;
    
    //构造方法默认就是private
    EnumDemo() {
        
    }
    
    public static String getMsg() {
        return "调用枚举实现单例的方法";
    }
}
public class Test {
    
    public static void main(String[] args) {
        //获取的是同一个实例,枚举底层定义的是一个常量
        EnumDemo demo1 =  EnumDemo.INSTANCE;
        EnumDemo demo2 =  EnumDemo.INSTANCE;
        System.out.println(demo2 == demo1);
        //调用方法
        EnumDemo.INSTANCE.getMsg();
    }
}

验证不能通过反射创建枚举实例

public class Test {
    
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //通过反射获取实例
        Constructor<EnumDemo> constructor = EnumDemo.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        //或抛出异常,不存在构造函数
        EnumDemo enumDemo = constructor.newInstance();
        System.out.println(enumDemo);
    }    
}
//反编译后的代码
public final class EnumDemo extends Enum
{

    public static EnumDemo[] values()
    {
        return (EnumDemo[])$VALUES.clone();
    }

    public static EnumDemo valueOf(String name)
    {
        return (EnumDemo)Enum.valueOf(com/kuangstudy/Singleton/EnumDemo, name);
    }
    // 构造函数为有参构造
    private EnumDemo(String s, int i)
    {
        super(s, i);
    }

    public EnumDemo getInstance()
    {
        return INSTANCE;
    }

    public static final EnumDemo INSTANCE;
    private static final EnumDemo $VALUES[];

    static
    {
        INSTANCE = new EnumDemo("INSTANCE", 0);
        $VALUES = (new EnumDemo[] {
        INSTANCE
        });
    }
}

验证反射中利用枚举的有参构造获取实例,是否可行

public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //通过反射获取实例,会抛出异常信息
// “Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects”
        Constructor<EnumDemo> constructor = EnumDemo.class.getDeclaredConstructor(String.class,int.class);
        constructor.setAccessible(true);
        EnumDemo enumDemo = constructor.newInstance();
        System.out.println(enumDemo);
    }

//newInstance()方法中已经表名枚举不能通过反射创建实例
image.png

java的序列化机制针对枚举是做特殊处理的。在序列化枚举时,只会存储枚举常量的名称。
反序列化过程中,会根据名字查找对应的枚举实例。

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        EnumDemo e1 = EnumDemo.INSTANCE;
        System.out.println(e1.hashCode());
        //将单例枚举对象序列化
        FileOutputStream fos = new FileOutputStream("D://test.txt");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(e1);
    
        //枚举的序列化是特殊处理的,并不是深拷贝,因此两次的hash值相同
        FileInputStream fis = new FileInputStream("D://test.txt");
        ObjectInputStream ois = new ObjectInputStream(fis);
        EnumDemo result = (EnumDemo) ois.readObject();
        System.out.println(result.hashCode());
    }

参考:https://blog.51cto.com/u_12617333/5997284


forest
0 声望1 粉丝