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


相关推荐

  • angular面试问题_kafka面试题

    angular面试问题_kafka面试题Angularv8+面试系列Angular面试题汇总1-基本知识Angular面试题汇总2-Component/Service目录Angular中的测试有哪些种,基于哪些测试框架什么是Karma?在Angular中有什么作用?什么是Jasmine?在Angular中有什么用?什么是protractor?单元测试UnitTest什么是Angular中的单元测试?AngularUT的最佳实践测试Service时,有其他依赖如何处理?端到端测试(e2e)Angular中的测试有哪些.

    2022年9月16日
    3
  • Eurake分区理解

    Eurake分区理解Eurake分区理解大型项目如果存在多个机房,例如北京机房,上海机房,杭州机房等,上千个服务注册在Eurake上面,我们的事例也分别部署在各个区域。这时候,由于机房存在不同的区域,北京的一个服务如果调用上海的一个服务,就可能发生延迟,服务的响应速度也会慢很多,这时候,我们可能期望,北京的服务生产者调用北京的服务消费着,这该怎么去操作?Eurake其实有个分区功能,什么是分区,就是北京有一个注册…

    2022年6月12日
    37
  • ENVI5.3.1使用Landsat 8影像进行图像融合「建议收藏」

    ENVI5.3.1使用Landsat 8影像进行图像融合「建议收藏」ENVI处理郑州地区遥感图像融合,提高空间分辨率

    2022年7月23日
    44
  • p6spy简介_p6教程

    p6spy简介_p6教程在公司项目中运用了这项技术,一开始不清楚这是干啥用的,在网上查找资料有所一定的了解,但是应该不够全面,希望可以评论指出。p6spy是数据库动态监控的一种框架,它可以使得数据库数据无缝拦截和操作,而不必对现有应用程序的代码作任何修改。P6Spy分发包包括P6Log,它是一个可记录任何Java应用程序的所有JDBC事务的应用程序。其配置完成使用时,可以进行数据访问性能的监测。下面我们来看一下…

    2022年9月28日
    2
  • 应用程序错误0x000000该内存不能为read怎么解决_电脑开机内存不能为read进不去桌面

    应用程序错误0x000000该内存不能为read怎么解决_电脑开机内存不能为read进不去桌面(转)explorer.exe应用程序错误:0x000000该内存不能为read的解决方法

    2025年8月15日
    3
  • java分页计算当前页_java分页计算[通俗易懂]

    java分页计算当前页_java分页计算[通俗易懂]java仿google的分页算法1.现将数据从数据库读取出来封装一个java类中,在java类中计算publicclassPage{//成员变量//当前页privateintnowpage;//总记录数privateintcountrecord;//总页数privateintcountpage;//当前页记录开始的位置privateintpageindex;//每页显示的…

    2022年10月3日
    5

发表回复

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

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