java单例模式代码实现方式_java单例模式实现方式

java单例模式代码实现方式_java单例模式实现方式JAVA常见的设计模式之单例模式 懒汉模式 懒汉式是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间(搬运工)。标准的懒汉模式classLazySingleton{//私有成员属性privateLazySingletonlazySingleton;//私有构造方法privateLazySingleto…

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

JAVA常见的设计模式之单例模式

  • 懒汉模式

             懒汉式是典型的时间换空间,也就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间(搬运工)。

标准的懒汉模式

class LazySingleton {
    // 私有成员属性
    private LazySingleton lazySingleton;
    
    // 私有构造方法
    private LazySingleton() {
    }
    
    // 公共的获取实例方法
    public LazySingleton getLazySingleton() {
        // 如果成员属性为空,则创建实例
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

单线程环境下,该单例模式只会有一个实例

public class TestDemo
{
    public static void main(String[] args) {
        LazySingleton lazySingleton = LazySingleton.getLazySingleton();
        LazySingleton lazySingleton2 = LazySingleton.getLazySingleton();
        System.out.println(lazySingleton == lazySingleton2);
    }
}

运行结果:

java单例模式代码实现方式_java单例模式实现方式

多线程模式下,可能会产生多个实例

public class TestDemo
{
    public static void main(String[] args) {
        new Thread(() -> {
            LazySingleton lazySingleton = LazySingleton.getLazySingleton();
            System.out.println(lazySingleton);
        }).start();
        new Thread(() -> {
            LazySingleton lazySingleton = LazySingleton.getLazySingleton();
            System.out.println(lazySingleton);
        }).start();
    }
}

运行结果:

java单例模式代码实现方式_java单例模式实现方式

初步改进

class LazySingleton {
    // 私有成员属性
    private static LazySingleton lazySingleton;

    // 私有构造方法
    private LazySingleton() {
    }

    // 公共的获取实例方法
    public synchronized static LazySingleton getLazySingleton() {
        // 如果成员属性为空,则创建实例
        if (lazySingleton == null) {
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }
}

缺点,每次调用方法都会加锁,效率低

再次改进

class LazySingleton {
    // 私有成员属性,使用volatile可以保证代码的有序性,防止指令重排
    private volatile static LazySingleton lazySingleton;

    // 私有构造方法
    private LazySingleton() {
    }

    // 公共的获取实例方法
    // 使用synchronized + 双重确认机制可以保证线程安全,但有可能存在指令重排,会导致创建多个实例
    public static LazySingleton getLazySingleton() {
        if (lazySingleton == null) {
            synchronized (LazySingleton.class) {
                if (lazySingleton == null) {
                    lazySingleton = new LazySingleton();
                }
            }
        }
        return lazySingleton;
    }
}

静态类部类单例

/**
 * 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性
 * 本质是一个懒汉模式,在类加载时才会初始化对象
 */
class InnerSingleton implements Serializable {

    private static class InnerSingletonHolder {
        private static InnerSingleton innerSingleton = new InnerSingleton();
    }

    private InnerSingleton() {
    }

    public static InnerSingleton getInnerSingleton() {
        return InnerSingletonHolder.innerSingleton;
    }

}

 静态类不类单例不会有线程安全问题,线程安全由类加载机制担保

恶汉模式

             饿汉式是典型的空间换时间,当类装载的时候就会创建类实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断了,节省了运行时间(搬运工)。 

// 利用类加载机制保证线程安全
class HungrySingleton {
    private static HungrySingleton hungrySingleton = new HungrySingleton();

    private HungrySingleton() {
    }

    public static HungrySingleton getHungrySingleton() {
        return hungrySingleton;
    }
}

 枚举单例模式

package com.hy.test.singletonDemo;

public enum EnumSingletonDemo {
    INSTANCE;

}

class EnumTest {
    public static void main(String[] args) {
        EnumSingletonDemo instance = EnumSingletonDemo.INSTANCE;
        EnumSingletonDemo instance2 = EnumSingletonDemo.INSTANCE;
        System.out.println(instance == instance2);
    }
}

运行结果:

java单例模式代码实现方式_java单例模式实现方式

单例模式可能出现的问题(都会用静态类不类单例举例)

反射攻击

/**
 * 测试demo
 *
 * @auther Hy
 * @date 2020/8/25
 */
public class TestDemo {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        InnerSingleton innerSingleton = InnerSingleton.getInnerSingleton();
        Class clazz = InnerSingleton.class;
        Constructor<InnerSingleton> declaredConstructor = clazz.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        InnerSingleton innerSingleton1 = (InnerSingleton) declaredConstructor.newInstance();
        System.out.println(innerSingleton == innerSingleton1);
    }
}

/**
 * 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性
 * 本质是一个懒汉模式,在类加载时才会初始化对象
 */
class InnerSingleton implements Serializable {

    private static class InnerSingletonHolder {
        private static InnerSingleton innerSingleton = new InnerSingleton();
    }

    private InnerSingleton() {
    }

    public static InnerSingleton getInnerSingleton() {
        return InnerSingletonHolder.innerSingleton;
    }

}

运行结果:

java单例模式代码实现方式_java单例模式实现方式

由此可见,反射生成了一个新的对象,不符合单例模式的定义

解决方法:在私有构造器中添加判断,如果已存在实例对象,抛出异常(也可进行其他操作,根据需求决定)

优化后的代码如下

/**
 * 测试demo
 *
 * @auther Hy
 * @date 2020/8/25
 */
public class TestDemo {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        InnerSingleton innerSingleton = InnerSingleton.getInnerSingleton();
        Class clazz = InnerSingleton.class;
        Constructor<InnerSingleton> declaredConstructor = clazz.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        InnerSingleton innerSingleton1 = (InnerSingleton) declaredConstructor.newInstance();
        System.out.println(innerSingleton == innerSingleton1);
    }
}

/**
 * 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性
 * 本质是一个懒汉模式,在类加载时才会初始化对象
 */
class InnerSingleton implements Serializable {

    private static class InnerSingletonHolder {
        private static InnerSingleton innerSingleton = new InnerSingleton();
    }

    private InnerSingleton() {
        // 防止反射攻击,只有恶汉与静态类部类能防止反射攻击
        if (InnerSingletonHolder.innerSingleton != null) {
            throw new RuntimeException("单例模式已存在一个实例");
        }
    }

    public static InnerSingleton getInnerSingleton() {
        return InnerSingletonHolder.innerSingleton;
    }

}

 运行结果:

java单例模式代码实现方式_java单例模式实现方式

注意:只有恶汉模式与静态类部类能防止反射攻击

序列化相关问题

 首先,我们对创建的实例进行序列化,代码如下:

/**
 * 测试demo
 *
 * @auther Hy
 * @date 2020/8/25
 */
public class TestDemo {
    public static void main(String[] args) throws IOException {
        InnerSingleton innerSingleton = InnerSingleton.getInnerSingleton();
        // 序列化测试
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("innerTest"));
        oos.writeObject(innerSingleton);
        oos.close();
        // 反序列化
/*        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("innerTest"));
        InnerSingleton innerSingleton1 = (InnerSingleton) ois.readObject();
        System.out.println(innerSingleton == innerSingleton1);*/
    }
}

/**
 * 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性
 * 本质是一个懒汉模式,在类加载时才会初始化对象
 */
class InnerSingleton implements Serializable {

    // 需要固定序列化版本号id,如果不固定,JVM会根据字段、方法等生成一个序列化ID,并存入对应的序列化文件,反序列化时,
    // 会按照相同规则生成一个序列化版本号进行对比,如果类已经发生了改变,反序列化的版本号会对应不上,反序列化会失败
    private static final long serialVersionUID = 7822769557659839582L;

    private static class InnerSingletonHolder {
        private static InnerSingleton innerSingleton = new InnerSingleton();
    }

    private InnerSingleton() {
        // 防止反射攻击,只有恶汉与静态类不类能防止反射攻击
        if (InnerSingletonHolder.innerSingleton != null) {
            throw new RuntimeException("单例已存在一个实例");
        }
    }

    public static InnerSingleton getInnerSingleton() {
        return InnerSingletonHolder.innerSingleton;
    }
    
}

然后,我们进行反序列化,查看反序列化生成的实例跟单例的实例是否是同一个

/**
 * 测试demo
 *
 * @auther Hy
 * @date 2020/8/25
 */
public class TestDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        InnerSingleton innerSingleton = InnerSingleton.getInnerSingleton();
        // 序列化测试
/*        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("innerTest"));
        oos.writeObject(innerSingleton);
        oos.close();*/
        // 反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("innerTest"));
        InnerSingleton innerSingleton1 = (InnerSingleton) ois.readObject();
        System.out.println(innerSingleton == innerSingleton1);
    }
}

/**
 * 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性
 * 本质是一个懒汉模式,在类加载时才会初始化对象
 */
class InnerSingleton implements Serializable {

    // 需要固定序列化版本号id,如果不固定,JVM会根据字段、方法等生成一个序列化ID,并存入对应的序列化文件,反序列化时,
    // 会按照相同规则生成一个序列化版本号进行对比,如果类已经发生了改变,反序列化的版本号会对应不上,反序列化会失败
    private static final long serialVersionUID = 7822769557659839582L;

    private static class InnerSingletonHolder {
        private static InnerSingleton innerSingleton = new InnerSingleton();
    }

    private InnerSingleton() {
        // 防止反射攻击,只有恶汉与静态类不类能防止反射攻击
        if (InnerSingletonHolder.innerSingleton != null) {
            throw new RuntimeException("单例已存在一个实例");
        }
    }

    public static InnerSingleton getInnerSingleton() {
        return InnerSingletonHolder.innerSingleton;
    }

}

运行结果:

java单例模式代码实现方式_java单例模式实现方式

由此可见,反序列化创建了一个新的实例

解决方法:Serializable的源码中给出了提示

java单例模式代码实现方式_java单例模式实现方式

/**
 * 测试demo
 *
 * @auther Hy
 * @date 2020/8/25
 */
public class TestDemo {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        InnerSingleton innerSingleton = InnerSingleton.getInnerSingleton();
        // 序列化测试
/*        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("innerTest"));
        oos.writeObject(innerSingleton);
        oos.close();*/
        // 反序列化
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("innerTest"));
        InnerSingleton innerSingleton1 = (InnerSingleton) ois.readObject();
        System.out.println(innerSingleton == innerSingleton1);
    }
}

/**
 * 由静态内部类持有单例对象,并调用外部类的私有构造器初始化,由外部类调用静态内部类的属性
 * 本质是一个懒汉模式,在类加载时才会初始化对象
 */
class InnerSingleton implements Serializable {

    // 需要固定序列化版本号id,如果不固定,JVM会根据字段、方法等生成一个序列化ID,并存入对应的序列化文件,反序列化时,
    // 会按照相同规则生成一个序列化版本号进行对比,如果类已经发生了改变,反序列化的版本号会对应不上,反序列化会失败
    private static final long serialVersionUID = 7822769557659839582L;

    private static class InnerSingletonHolder {
        private static InnerSingleton innerSingleton = new InnerSingleton();
    }

    private InnerSingleton() {
        // 防止反射攻击,只有恶汉与静态类不类能防止反射攻击
        if (InnerSingletonHolder.innerSingleton != null) {
            throw new RuntimeException("单例已存在一个实例");
        }
    }

    public static InnerSingleton getInnerSingleton() {
        return InnerSingletonHolder.innerSingleton;
    }

    // 反序列化时,如果是单例模式,需要重写该方法,返回单例的实例,否则会获取到不同的对象
    Object readResolve() throws ObjectStreamException {
        return InnerSingletonHolder.innerSingleton;
    }
}

运行结果:

java单例模式代码实现方式_java单例模式实现方式

因此,在工作中推荐大家使用静态类部类单例模式,可以有效的防止反射攻击与序列化带来的相关问题

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/169696.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 文件上传的三种方式-Java「建议收藏」

    文件上传的三种方式-Java「建议收藏」前言:因自己负责的项目(jetty内嵌启动的SpringMvc)中需要实现文件上传,而自己对java文件上传这一块未接触过,且对Http协议较模糊,故这次采用渐进的方式来学习文件上传的原理与实践。该博客重在实践。一.Http协议原理简介   HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用与发展,

    2022年5月14日
    110
  • 《异常检测——从经典算法到深度学习》6 基于重构概率的 VAE 异常检测

    《异常检测——从经典算法到深度学习》6 基于重构概率的 VAE 异常检测目录0概论1基于隔离森林的异常检测算法2基于LOF的异常检测算法3基于One-ClassSVM的异常检测算法4基于高斯概率密度异常检测算法5Opprentice——异常检测经典算法最终篇6基于VAE的异常检测算法6.基于VAE的异常检测算法论文名称:VariationalAutoencoderbasedAnomalyDetectionusingReconstructionProbability发表时间:2015.12立即下载论文总体

    2025年6月9日
    3
  • Windows系统下配置允许Redis远程访问

    Windows系统下配置允许Redis远程访问

    2020年11月9日
    208
  • ResNet34_keras dropout

    ResNet34_keras dropout参考:https://www.kaggle.com/meaninglesslives/unet-resnet34-in-keras

    2022年10月6日
    2
  • web navigator_前端如何传参数为一个对象

    web navigator_前端如何传参数为一个对象Navigator对象一、Navigator对象简介二、Navigator对象集合三、Navigator对象属性1.appCodeName、appName、appVersion(1)属性介绍(2)具体演示2.cookieEnabled、onLine(1)属性介绍(2)具体演示3.platform、userAgent(1)属性介绍(2)具体演示四、Navigator对象方法1.javaEnabled()五、Navigator对象描述一、Navigator对象简介Navigator对象包含有关浏览

    2025年8月29日
    4
  • 104规约(持续更新….)

    104规约(持续更新….)1. 固定帧:启动字符+长度+控制域一+ 控制域二+ 控制域三+ 控制域四常见报文:启动链路:680407000000      (U帧)启动链路确认:68040B000000   (U帧)测试帧:680443000000         (U帧)测试确认:680483000000…

    2022年6月20日
    44

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号