单例模式

发布时间:2022-07-01 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了单例模式脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

单例模式,是一种常用的软件设计模式。通过单例模式可以保证系统中,应用该模式的这个类永远只有一个实例。即一个类永远只有一个对象实例。

1.1 饿汉式单例

代码示例

package cn.Guardwhy.singleDemo01;
/**
 单例的应用场景:在实例开发中,有很多业务对象永远只需要一个,无论启动多少次
                我们只需要一个对象,例如任务管理对象,只需要一个。节约内存和性能。

单例的设计方式:
    (1)饿汉单例设计模式
         在用类的时候,对象已经创建好了。
         步骤:
         a.定义一个对象,最好static和final修饰,这样这个对象永远是唯一不可变的对象了。
         b.把构造器进行私有化,外面就不能创建新对象。
         c.提供一个方法把唯一的单例对象返回出去。
 */
public class HungrySingle01 {
    public static void main(String[] args) {
        // 4.创建对象
        MyHungrySingle01 single1 = MyHungrySingle01.getInstance();
        MyHungrySingle01 single2 = MyHungrySingle01.getInstance();
        // 5.输出结果
        System.out.PRintln(single1 == single2);	// true
    }
}

class MyHungrySingle01{
    // 1.定义一个对象
    private static final MyHungrySingle01 instance = new MyHungrySingle01();
    // 2.将构造器私有化
    public MyHungrySingle01() {
        
    }

    // 3.返回唯一的对象
    public static MyHungrySingle01 getInstance(){
        return instance;
    }
}

1.2 静态内部类

在类中定义static变量的对象,并且直接初始化。既保证了线程的安全性,同时又满足了懒加载。这是饿汉式的改进版本。

package cn.guardwhy.singleDemo01;
// 静态内部类
public class Holder {
    // 1. 构造器
    private Holder(){

    }

    public  static  Holder getInstance(){
        return InnerClass.HOLDER;
    }

    public static class InnerClass{
        private static final Holder HOLDER = new Holder();
    }
}

1.3 懒汉式单例

正常的懒汉式单例

代码示例

package cn.guardwhy.singleDemo01;
/*
懒汉单例设计模式
    真正需要的时候,才创建一个对象。
        步骤:
         a.定义一个对象变量用于后面存储一个对象,此时是没有创建对象的。
         b.把构造器进行私有化,外面就不能创建新对象。
         c.提供一个方法,等需要对象的时候判断是否有一个唯一对象,如果没有创建一个对象。
            以后都是直接返回这个对象即可!!
*/
public class LazySingle02 {
    public static void main(String[] args) {
        MyLazySingle02 t1 = MyLazySingle02.getInstance();
        MyLazySingle02 t2 = MyLazySingle02.getInstance();
        // 输出结果
        System.out.println(t1); // cn.guardwhy.singleDemo01.MyLazySingle02@49e4cb85
        System.out.println(t1); // cn.guardwhy.singleDemo01.MyLazySingle02@49e4cb85
        System.out.println(t1 == t2); // true
    }
}

// 懒汉式单例模式
class MyLazySingle02{
    // 1.定义一个对象变量用于保存一个对象,但是这里还没有创建对象
    private static MyLazySingle02 instance = null;
    // 2.把构造器私有起来
    private MyLazySingle02(){

    }
    // 3.返回这个唯一的对象
    public static MyLazySingle02 getInstance(){
        // 3.1 懒汉模式是现在需要,才创建对象。
        if(instance == null){
            instance = new MyLazySingle02();
        }
        // 3.2 返回对象
        return instance;
    }
}

1.4 DCL懒汉式

代码示例

package cn.guardwhy.singleDemo02;

/*
 懒汉式单例
*/
public class LazySingle {
    // 1.将构造器私有化
    public LazySingle() {
        System.out.println(Thread.currentThread().getName() + " ok");
    }

    // 2.定义lazySingle
    private  static LazySingle lazySingle;
    // 3.双重检查锁模式 懒汉式单例 DCL懒汉式
    public static LazySingle getInstance(){
        // 3.1 条件判断
        if(lazySingle == null){
            synchronized (LazySingle.class){
                if(lazySingle == null){
                   // 不是一个原子性操作
                    lazySingle = new LazySingle();  
                }
            }
        }
        // 4.返回对象
        return lazySingle;
    }

    // 多线程并发
    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazySingle.getInstance();
            }).start();
        }
    }
}

DCL懒汉式的单例,保证了线程的安全性,又符合了懒加载,只有在用到的时候,才会去初始化,调用效率也比较高,但是这种写法在极端情况还是可能会有一定的问题。因为不是原子性操作,至少会经过三个步骤:

lazySingle = new LazySingle(); 
  1. 分配对象内存空间
  2. 执行构造方法初始化对象
  3. 设置instance指向刚分配的内存地址,此时instance !=null;

注意点

由于指令重排,导致A线程执行 lazySingle = new LazySingle( );的时候,可能先执行了第三步(还没执行第二步),此时线程B又进来了,发现lazySingle已经不为空了,直接返回了lazySingle,并且后面使用了返回的lazySingle,由于线程A还没有执行第二步,导致此时lazySingle还不完整,可能会有一些意想不到的错误,这时候增加一个volatile关键字来避免指令重排

代码示例

package cn.guardwhy.singleDemo02;

/*
 懒汉式单例
*/
public class LazySingle {
    // 1.将构造器私有化
    public LazySingle() {
        System.out.println(Thread.currentThread().getName() + " ok");
    }

    // 2. volatile关键字来避免指令重排
    private volatile static LazySingle lazySingle;
    // 3.双重检查锁模式 懒汉式单例 DCL懒汉式
    public static LazySingle getInstance(){
        // 3.1 条件判断
        if(lazySingle == null){
            synchronized (LazySingle.class){
                if(lazySingle == null){
                    /*
                     1.分配对象内存空间
                     2. 执行构造方法初始化对象
                     3. 设置instance指向刚分配的内存地址,此时instance !=null;
                    */
                    lazySingle = new LazySingle();  // 不是一个原子性操作
                }
            }
        }
        // 4.返回对象
        return lazySingle;
    }

    // 多线程并发
    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazySingle.getInstance();
            }).start();
        }
    }
}

1.5 反射破坏单例

15.1 情况 1

通过反射破坏单例模式,反射无视private修饰的构造方法,可以直接在外面newInstance

代码示例

package cn.guardwhy.singleDemo02;

import java.lang.reflect.Constructor;

/*
 懒汉式单例
*/
public class LazySingle {
    // 1.将构造器私有化
    public LazySingle() {
        System.out.println(Thread.currentThread().getName() + " ok");
    }

    // 2. volatile关键字来避免指令重排
    private volatile static LazySingle lazySingle;
    // 3.双重检查锁模式 懒汉式单例 DCL懒汉式
    public static LazySingle getInstance(){
        // 3.1 条件判断
        if(lazySingle == null){
            synchronized (LazySingle.class){
                if(lazySingle == null){
                   
                    lazySingle = new LazySingle();  // 不是一个原子性操作
                }
            }
        }
        // 4.返回对象
        return lazySingle;
    }

    public static void main(String[] args) throws Exception {
        // 1.创建对象instance1
        LazySingle instance1 = LazySingle.getInstance();
        // 2.获得反射对象
        Constructor<LazySingle> declaredConstructor = LazySingle.class.getDeclaredConstructor(null);
        // 3.无视私有构造器
        declaredConstructor.setAccessible(true);
        // 4.通过反射创建对象instance2
        LazySingle instance2 = declaredConstructor.newInstance();
        
        System.out.println(instance1.hashCode());   // 460141958
        System.out.println(instance2.hashCode());   // 1163157884
        // 3.判断是否相等
        System.out.println(instance1 == instance2); // false
    }
}

解决方案

// 1.将构造器私有化
public LazySingle() {
    synchronized (LazySingle.class){
        // 条件判断
        if(lazySingle != null){
            throw new RuntimeException("反射破坏失败!!!");
        }
    }
}

执行结果

单例模式

13.4.2 情况 2

先调用getInstance方法,一上来就直接用反射创建对象,判断就不生效了。

代码示例

package cn.guardwhy.singleDemo02;

import java.lang.reflect.Constructor;

/*
 懒汉式单例
*/
public class LazySingle {
    // 1.将构造器私有化
    public LazySingle() {
        synchronized (LazySingle.class){
            // 条件判断
            if(lazySingle != null){
                throw new RuntimeException("反射破坏失败!!!");
            }
        }
    }

    // 2. volatile关键字来避免指令重排
    private volatile static LazySingle lazySingle;
    // 3.双重检查锁模式 懒汉式单例 DCL懒汉式
    public static LazySingle getInstance(){
        // 3.1 条件判断
        if(lazySingle == null){
            synchronized (LazySingle.class){
                if(lazySingle == null){
                    lazySingle = new LazySingle();  // 不是一个原子性操作
                }
            }
        }
        // 4.返回对象
        return lazySingle;
    }

    public static void main(String[] args) throws Exception {

        // 1.获得反射对象
        Constructor<LazySingle> declaredConstructor = LazySingle.class.getDeclaredConstructor(null);
        // 2.无视私有构造器
        declaredConstructor.setAccessible(true);
        // 3.通过反射创建对象instance2
        LazySingle instance2 = declaredConstructor.newInstance();
        LazySingle instance1 = declaredConstructor.newInstance();

        System.out.println(instance1.hashCode());   // 460141958
        System.out.println(instance2.hashCode());   // 1163157884
        // 4.判断是否相等
        System.out.println(instance1 == instance2); // false
    }
}

解决方案

// 0.定义标志符
private static boolean flag = false;
// 1.将构造器私有化
public LazySingle() {
    synchronized (LazySingle.class){
        // 条件判断
        if(flag == false){
            flag = true;

        }else{
            throw new RuntimeException("反射破坏失败!!!");
        }
    }
}

执行结果

单例模式

13.4.3 情况 3

定义一个boolean变量flag,初始值是false,私有构造函数里面做一个判断,如果flag=false,就把flag改为true,但是如果flag等于true,就说明有问题了,因为正常的调用是不会第二次跑到私有构造方法的,所以抛出异常。看起来很美好,但是还是不能完全止反射破坏单例模式,因为可以利用反射修改flag的值

代码示例

package cn.guardwhy.singleDemo02;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

/*
 懒汉式单例
*/
public class LazySingle {
    // 0.定义标志符
    private static boolean flag = false;
    // 1.将构造器私有化
    public LazySingle() {
        synchronized (LazySingle.class){
            // 条件判断
            if(flag == false){
                flag = true;

            }else{
                throw new RuntimeException("反射破坏失败!!!");
            }
        }
    }

    // 2. volatile关键字来避免指令重排
    private volatile static LazySingle lazySingle;
    // 3.双重检查锁模式 懒汉式单例 DCL懒汉式
    public static LazySingle getInstance(){
        // 3.1 条件判断
        if(lazySingle == null){
            synchronized (LazySingle.class){
                if(lazySingle == null){
                    lazySingle = new LazySingle();  // 3.2不是一个原子性操作
                }
            }
        }
        // 4.返回对象
        return lazySingle;
    }

    public static void main(String[] args)  {

        try {
            // 拿到隐藏字段
            Field flag = LazySingle.class.getDeclareDField("flag");
            // 破坏字段的私有权限
            flag.setAccessible(true);

            // 获得反射对象
            Constructor<LazySingle> declaredConstructor = LazySingle.class.getDeclaredConstructor(null);
            // 无视私有构造器
            declaredConstructor.setAccessible(true);
            LazySingle instance1 = declaredConstructor.newInstance();
            // 修改对象1的值
            flag.set(instance1, false);
            // 通过反射创建对象instance2
            LazySingle instance2 = declaredConstructor.newInstance();

            System.out.println(instance1.hashCode());   // 1163157884
            System.out.println(instance2.hashCode());   // 1956725890
            // 判断是否相等
            System.out.println(instance1 == instance2); // false
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

13.5 枚举类型

枚举类型是Java 5中新增特性的一部分,它是一种特殊的数据类型,之所以特殊是因为它既是一种类(class)类型却又比类类型多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁性、安全性以及便捷性。

13.5.1 源码分析

package cn.guardwhy.singleDemo02;

public enum  EnumSingleton {
    // 定义一个单例对象
   INSTANCE;
   public EnumSingleton getInstance(){
       // 返回对象
       return INSTANCE;
   }

    public static void main(String[] args) {
        EnumSingleton singleton1 = EnumSingleton.INSTANCE;
        EnumSingleton singleton2 = EnumSingleton.INSTANCE;
        // 结果判断
        System.out.println("两个实例是否相等:" + (singleton1==singleton2)); // 两个实例是否相等:true
    }
}

通过查看newInstance的可得知

单例模式

13.5.2 尝试破坏

通过 jad工具进行反编译

jad -s java EnumSingleton.class
# 会生成一个java文件
Parsing EnumSingleton.class... Generating EnumSingleton.java

查看源码

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.COM/jad.htML
// Decompiler options: packimports(3) 
// Source File Name:   EnumSingleton.java

package cn.guardwhy.singleDemo02;

import java.io.PrintStream;

public final class EnumSingleton extends Enum
{

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

    public static EnumSingleton valueOf(String name)
    {
        return (EnumSingleton)Enum.valueOf(cn/guardwhy/singleDemo02/EnumSingleton, name);
    }

    private EnumSingleton(String s, int i)
    {
        suPEr(s, i);
    }

    public EnumSingleton getInstance()
    {
        return INSTANCE;
    }

    public static void main(String args[])
    {
        EnumSingleton singleton1 = INSTANCE;
        EnumSingleton singleton2 = INSTANCE;
        System.out.println((new StringBuilder()).append("u4E24u4E2Au5B9Eu4F8Bu662Fu5426u76F8u7B49:").append(singleton1 == singleton2).toString());
    }

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

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

代码示例

package cn.guardwhy.singleDemo02;

import java.lang.reflect.Constructor;

public enum  EnumSingleton {
    // 定义一个单例对象
   INSTANCE;
   public EnumSingleton getInstance(){
       // 返回对象
       return INSTANCE;
   }

    public static void main(String[] args) {
        EnumSingleton singleton1 = EnumSingleton.INSTANCE;
        try {
            // 获得反射对象
            Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
            // 破解私有构造器
            declaredConstructor.setAccessible(true);
            // 通过反射创建对象singleton2
            EnumSingleton singleton2 = declaredConstructor.newInstance();

            // 输出结果
            System.out.println(singleton1);
            System.out.println(singleton2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

执行结果

单例模式

脚本宝典总结

以上是脚本宝典为你收集整理的单例模式全部内容,希望文章能够帮你解决单例模式所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。