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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 学习双拼必看:双拼输入法的心得以及快速入门办法

    学习双拼必看:双拼输入法的心得以及快速入门办法1.简单介绍一下双拼2.总共18种双拼方案3.15种双拼方案的具体映射4.顺便提一下双拼口诀的事情5.总结不同平台选择的方案双拼(也称双打)是一种建立在拼音输入法基础上的输入方法,可视为全拼的一种改进,它通过将汉语拼音中每个含多个字母的声母或韵母各自映射到某个按键上,使得每个音都可以用两个按键打出,极大地提高了拼音输入法的输入速度。这种声母或韵母到按键的对应表通常称之为双…

    2022年6月23日
    43
  • 【离散数学】平面图

    【离散数学】平面图介绍图论中的平面图

    2022年5月26日
    34
  • allure command

    allure commandallure-Usage:allure[options][command][commandoptions]Options:–helpPrintcommandlinehelp.-q,–quietSwitchonthequietmode.Default:false-v,–verboseSwitchontheverbosemode.Default:false…

    2022年7月26日
    5
  • idea最新激活码2022【2021免费激活】

    (idea最新激活码2022)最近有小伙伴私信我,问我这边有没有免费的intellijIdea的激活码,然后我将全栈君台教程分享给他了。激活成功之后他一直表示感谢,哈哈~https://javaforall.net/100143.htmlIntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,上面是详细链接哦~0HKL…

    2022年3月31日
    51
  • windows 安装opencv(AIK批量部署win)

    windows 安装opencv(AIK批量部署win)一、简介OpenCV的全称是OpenSourceComputerVisionLibrary,是一个跨平台的计算机视觉库。OpenCV是由英特尔公司发起并参与开发,以BSD许可证授权发行,可以在商业和研究领域中免费使用。OpenCV可用于开发实时的图像处理、计算机视觉以及模式识别程序。该程序库也可以使用英特尔公司的IPP进行加速处理。OpenCV用C++语言编写,它的主要接口也是C+…

    2022年4月18日
    45
  • 微信第三方登录接口购买_微信授权登录第三方网页

    微信第三方登录接口购买_微信授权登录第三方网页随着手机微信的崛起,腾讯发布的微信联登确实很诱惑pc端的伙伴们,现在就说说在pc端用微信扫一扫实现微信第三方登陆的方式。  第一步:获取AppID AppSecret(不做解释,自己去微信公众平台申请)第二步:生成扫描二维码,获取codehttps://open.weixin.qq.com/connect/qrconnect?appid=AppID&amp;redirect_uri=http://…

    2022年10月31日
    0

发表回复

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

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