IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 设计模式之单例模式 -> 正文阅读

[Java知识库]设计模式之单例模式

单例模式特点

1.构造方法私有化
2.实例化变量引用私有化
3.获取实例方法共有

饿汉式实现

    public class Singleton {
      /*
       * 利用静态变量来记录Singleton的唯一实例
       * 直接初始化静态变量,这样就可以确保线程安全了
       */
      private static Singleton instance= new Singleton();
 
      /*
       * 构造器私有化,只有Singleton类内才可以调用构造器
      */
     private Singleton(){

     }

     public static Singleton getInstance(){
         return instance;
     }

 }

懒汉式双重锁机制

  public class Singleton {
      /*
       * 利用静态变量来记录Singleton的唯一实例
       * volatile 关键字确保:当uniqueInstance变量被初始化成Singleton实例时,
       * 多个线程正确地处理uniqueInstance变量
       *
       */
      private volatile static Singleton instance;
 
     /*
      * 构造器私有化,只有Singleton类内才可以调用构造器
      */
     private Singleton(){

     }

     /*
      *
      * 检查实例,如果不存在,就进入同步区域
      */
     public static Singleton getInstance(){
         if(instance== null){
             synchronized(Singleton.class){    //进入同步区域
                 if(instance== null){     //在检查一次,如果为null,则创建
                     instance= new Singleton();
                 }
             }
         }
         return instance;
     }

 }

静态内部类

public class Singleton {    
    private static class LazyHolder {    
       private static final Singleton INSTANCE = new Singleton();    
    }    
    private Singleton (){}    
    public static final Singleton getInstance() {    
       return LazyHolder.INSTANCE;    
    }    
}

单例可能产生的问题

1.    《effective java》中只简单的提了几句话:“享有特权的客户端可以借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器。如果需要低于这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。

2.    任何一个readObject方法,不管是显式的还是默认的,它都会返回一个新建的实例,这个新建的实例不同于该类初始化时创建的实例。”当然,这个问题也是可以解决的,想详细了解的同学可以翻看《effective java》第77条:对于实例控制,枚举类型优于readResolve

以上问题怎样解决:枚举单例实现
Java规范字规定,每个枚举类型及其定义的枚举变量在JVM中都是唯一的,因此在枚举类型的序列化和反序列化上,Java做了特殊的规定。在序列化的时候Java仅仅是将枚举对象的name属性输到结果中,反序列化的时候则是通过java.lang.Enum的valueOf()方法来根据名字查找枚举对象。也就是说,序列化的时候只将DATASOURCE这个名称输出,反序列化的时候再通过这个名称,查找对应的枚举类型,因此反序列化后的实例也会和之前被序列化的对象实例相同。

public enum SingletonEnum {
    DATASOURCE;
    
    private DBConnection connection = null;

    private SingletonEnum() {
        connection = new DBConnection();
    }

    public DBConnection getConnection() {
        return connection;
    }
}
//需要单例的实际对象
class DBConnection {
}

class Test {
    public static void main(String[] args) {
        DBConnection conn1 = SingletonEnum.DATASOURCE.getConnection();
        DBConnection conn2 = SingletonEnum.DATASOURCE.getConnection();
        System.out.println(conn1 == conn2);//true
    }
}

javap -c class文件

D:\dest\sso\target\classes\com\explain>javap -c SingletonEnum.class
Compiled from "SingletonEnum.java"
//其实就是继承了Enum  
public final class com.explain.SingletonEnum extends java.lang.Enum<com.explain.SingletonEnum> {
  public static final com.explain.SingletonEnum DATASOURCE;

  public static com.explain.SingletonEnum[] values();
    Code:
       0: getstatic     #1                  // Field $VALUES:[Lcom/explain/SingletonEnum;
       3: invokevirtual #2                  // Method "[Lcom/explain/SingletonEnum;".clone:()Ljava/lang/Object;
       6: checkcast     #3                  // class "[Lcom/explain/SingletonEnum;"
       9: areturn

  public static com.explain.SingletonEnum valueOf(java.lang.String);
    Code:
       0: ldc           #4                  // class com/explain/SingletonEnum
       2: aload_0
       3: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
       6: checkcast     #4                  // class com/explain/SingletonEnum
       9: areturn

  public com.explain.DBConnection getConnection();
    Code:
       0: aload_0
       1: getfield      #7                  // Field connection:Lcom/explain/DBConnection;
       4: areturn

  static {};
    Code:
       0: new           #4                  // class com/explain/SingletonEnum
       3: dup
       4: ldc           #10                 // String DATASOURCE
       6: iconst_0
       7: invokespecial #11                 // Method "<init>":(Ljava/lang/String;I)V
      10: putstatic     #12                 // Field DATASOURCE:Lcom/explain/SingletonEnum;
      13: iconst_1
      14: anewarray     #4                  // class com/explain/SingletonEnum
      17: dup
      18: iconst_0
      19: getstatic     #12                 // Field DATASOURCE:Lcom/explain/SingletonEnum;
      22: aastore
      23: putstatic     #1                  // Field $VALUES:[Lcom/explain/SingletonEnum;
      26: return
}

通过反射实例化对象

public static void main(String[] args) throws Exception{
        DBConnection conn1 = SingletonEnum.DATASOURCE.getConnection();
        DBConnection conn2 = SingletonEnum.DATASOURCE.getConnection();
        System.out.println(conn1 == conn2);//true
        Constructor<SingletonEnum> declaredConstructor = SingletonEnum.class.getDeclaredConstructor(String.class, int.class);
        declaredConstructor.setAccessible(true);
        SingletonEnum singletonEnum =  declaredConstructor.newInstance();
    }
	反射在通过newInstance创建对象时,会检查该类是否ENUM修饰,如果是则抛出异常,反射失败。报错。
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
	at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
	at com.explain.Test.main(SingletonEnum.java:30)
	我们可以清楚地看到 newInstance 这段代码
	(clazz.getModifiers() & Modifier.ENUM) != 0
	如果是则抛出异常,反射失败
    @CallerSensitive
    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

那它是如何避免序列号问题的呢?

public enum  EnumSingleton implements Serializable {
    INSTANCE;
    private  String content;
    public String getContent() {
        return content;
    }
    public void setContent(String content) {
        this.content = content;
    }
    private EnumSingleton() {
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        EnumSingleton s = EnumSingleton.INSTANCE;
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("EnumSingleton.obj"));
        oos.writeObject(s);
        oos.flush();
        oos.close();

        FileInputStream fis = new FileInputStream("EnumSingleton.obj");
        ObjectInputStream ois = new ObjectInputStream(fis);
        EnumSingleton s1 = (EnumSingleton)ois.readObject();
        ois.close();
        System.out.println(s+"\n"+s1);
        System.out.println("枚举序列化后读取其中的内容:"+s1.getContent());
        System.out.println("枚举序列化前后两个是否同一个:"+(s==s1));
    }
}

//输出
枚举序列化前读取其中的内容:枚举单例序列化
INSTANCE
INSTANCE
枚举序列化后读取其中的内容:枚举单例序列化
枚举序列化前后两个是否同一个:true
枚举类型的序列化和反序列化上,Java做了特殊的规定。在序列化的时候Java仅仅是将枚举对象的name属性输到结果中,反序列化的时候则是通过java.lang.Enum的valueOf()方法来根据名字查找枚举对象。也就是说,序列化的时候只将DATASOURCE这个名称输出,反序列化的时候再通过这个名称,查找对应的枚举类型,因此反序列化后的实例也会和之前被序列化的对象实例相同。
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-08-29 08:57:09  更:2021-08-29 08:59:10 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 13:17:18-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码