java中的设计模式&单例模式

简介: java中的设计模式&单例模式

单例设计模式讲解内容(由于最近工作也是比较饱和时间有限,后续会将文章相关源码托管到git上、请大家原谅)

1、单例模式模式的应用场景

  单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并 提供一个全局访问点。单例模式是创建型模式。单例模式在现实生活中应用也非常广泛。 常见的如:ServletContext、 ServletContextConfig 等;在 Spring 框架应用中 ApplicationContext;数据库的连接 池也都是单例形式。

2、常见的单例模式写法以及如何在多线程下保证单例的安全

  a、饿汉式单例

   饿汉式单例是在类加载的时候就立即初始化,并且创建单例对象。绝对线程安全,在线 程还没出现以前就是实例化了,不可能存在访问安全问题。

   优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。

   缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存。

   饿汉单例实现一:

  public class DszSingleton {

         //先静态、后动态

        //先属性、后方法

        //先上后下

        private static final DszSingleton dszSingleton = new DszSingleton ();

        private DszSingleton (){    

            //即使构造私有了,但是还是要防止反射强吻攻击
             if(null != dszSingleton ){
                    throw  new RuntimeException("不要乱搞,别以为不知道你在用反射");
             }
        }

       public static DszSingleton getInstance(){

               return DszSingleton ;

        }

  }

饿汉单例(静态代码加载机制)实现二:

  public class DszSingleton {

             private static final DszSingleton dszSingleton ;

             static { //由此加载

                           dszSingleton = new DszStaticSingleton();

              }

             private DszStaticSingleton(){

             //即使构造私有了,但是还是要防止反射强吻攻击
             if(null != dszSingleton ){
                    throw  new RuntimeException("不要乱搞,别以为不知道你在用反射");
             }
             }

             public static DszStaticSingleton getInstance(){

                         return dszSingleton;

             }

  }

  b、懒汉式单例

       懒汉式单例的特点是:被外部类调用的时候内部类才会加载,下面看懒汉式单例的简单

       懒汉单例实现一:

       //懒汉式单例 //在外部需要使用的时候才进行实例化

      public class LazySimpleSingleton {

                               //静态块,公共内存区域

                               private static LazySimpleSingleton lazy = null;

                               private LazySimpleSingleton(){   

                   //即使构造私有了,但是还是要防止反射强吻攻击
                   if(null != lazy){
                          throw  new RuntimeException("不要乱搞,别以为不知道你在用反射");
                   }
                                }

                               public static LazySimpleSingleton getInstance(){

                                        if(lazy == null){ //当然这里在多线程下是不安全的,下面会给大家展示这种解决方案

                                                 lazy = new LazySimpleSingleton();

                                         }

                                      return lazy;

                                   }

       }

       懒汉单例(优化)实现二:

       public class LazyDoubleCheckSingleton {

                            private volatile static LazyDoubleCheckSingleton lazy = null;

                            private LazyDoubleCheckSingleton(){        

                   //即使构造私有了,但是还是要防止反射强吻攻击
                   if(null != lazy){
                          throw  new RuntimeException("不要乱搞,别以为不知道你在用反射");
                   }
                            }

                            public static LazyDoubleCheckSingleton getInstance(){

                                    if(lazy == null){//第一层判定

                                               synchronized (LazyDoubleCheckSingleton.class){

                                                          if(lazy == null){//第二层判定

                                                                               lazy = new LazyDoubleCheckSingleton();

                                                            }

                                                }

                                          }

                                     return lazy;

                                     }

                              }

       c、注册式单例

         注册式单例又称为登记式单例,就是将每一个实例都登记到某一个地方,使用唯一的标 识获取实例。注册式单例有两种写法:一种为容器缓存,一种为枚举登记。

         ->枚举式单例                    

           public enum EnumSingleton {
                          INSTANCE;
                          private Object data;
                          public Object getData() {
                                        return data;
                          }
                         public void setData(Object data) {
                                       this.data = data;
                         }
                         public static EnumSingleton getInstance(){
                                         return INSTANCE;
                        }
              }

         //------MainTest------ 

public static void main(String[] args) {

try {
    EnumSingleton instance1 = null;

    EnumSingleton instance2 = EnumSingleton.getInstance();
    instance2.setData(new Object());

    FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    oos.writeObject(instance2);
    oos.flush();
    oos.close();

    FileInputStream fis = new FileInputStream("EnumSingleton.obj");
    ObjectInputStream ois = new ObjectInputStream(fis);
    instance1 = (EnumSingleton) ois.readObject();
    ois.close();

    System.out.println(instance1.getData());
    System.out.println(instance2.getData());
    System.out.println(instance1.getData() == instance2.getData());

}catch (Exception e){
    e.printStackTrace();
}

}
运行结果:

为什么枚举可以保证单例的安全?让我们来分析一下原理,这里大家需要安装一下Java 反编译工具 Jad

找到工程所 在的 class 目录,复制 EnumSingleton.class

然后切回到命令行,切换到工程所在的 Class 目录,输入命令 jad EnumSingleton.class,我们会在 Class 目录下会多一个 EnumSingleton.jad 文件。

打开 EnumSingleton.jad 文件我们惊奇又巧妙地发现有如下代码:

原来,枚举式单例在静态代码块中就给 INSTANCE 进行了赋值,是饿汉式单例的实现。

所以序列化和反射都是无法破坏枚举生成的单例,这里序列化不能破坏枚举单例就不分析了,其实跟下面讲的序列化readResolve类似场景,大家可以仿照去分析一下。

这里来分析一下反射为什么不能破坏枚举单例原理:

测试代码会报异常:

public static void main(String[] args) {

try {
    Class clazz = EnumSingleton.class;
    Constructor c = clazz.getDeclaredConstructor(String.class,int.class);
    c.setAccessible(true);
    EnumSingleton enumSingleton = (EnumSingleton)c.newInstance("duasnhouzhi ",666);

}catch (Exception e){
    e.printStackTrace();
}

}

这时错误已经非常明显了,告诉我们 Cannot reflectively create enum objects,不能 用反射来创建枚举类型。还是习惯性地想来看看 JDK 源码,进入 Constructor 的 newInstance()方法:

@CallerSensitive
public T newInstance(Object... var1) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {

if (!this.override && !Reflection.quickCheckMemberAccess(this.clazz, this.modifiers)) {
    Class var2 = Reflection.getCallerClass();
    this.checkAccess(var2, this.clazz, (Object)null, this.modifiers);
}

if ((this.clazz.getModifiers() & 16384) != 0) {
    //这里意思是在 newInstance()方法中做了强制性的判断,如果修饰符是 Modifier.ENUM 枚举类型, 直接抛出异常。
    //这里就给大家分析了为什么java发序列化为什么不能破坏枚举单例
    throw new IllegalArgumentException("Cannot reflectively create enum objects");
} else {
    ConstructorAccessor var4 = this.constructorAccessor;
    if (var4 == null) {
        var4 = this.acquireConstructorAccessor();
    }

    Object var3 = var4.newInstance(var1);
    return var3;
}

}

->容器缓存式单例

//Spring中的做法,就是用这种注册式单例
public class ContainerSingleton {

private ContainerSingleton(){}
private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();
public static Object getInstance(String className){
    synchronized (ioc) {
        if (!ioc.containsKey(className)) {
            Object obj = null;
            try {
                obj = Class.forName(className).newInstance();
                ioc.put(className, obj);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return obj;
        } else {
            return ioc.get(className);
        }
    }
}

}
容器式写法适用于创建实例非常多的情况,便于管理。但是,是非线程安全的。

d、利用ThreadLocal实现单例

public class ThreadLocalSingleton {

private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
        new ThreadLocal<ThreadLocalSingleton>(){
            @Override
            protected ThreadLocalSingleton initialValue() {
                return new ThreadLocalSingleton();
            }
        };

private ThreadLocalSingleton(){}

public static ThreadLocalSingleton getInstance(){
    return threadLocalInstance.get();
}

}
这里简单讲一下ThreadLocal为什么实现单例,这是根据他的特点决定的,它是一个独占线程安全工具。具体大家可以先去了解一下,这里就不做具体分析了。

e、内部类实现单例

//这种形式兼顾饿汉式的内存浪费,也兼顾synchronized性能问题
//完美地屏蔽了这两个缺点
public class LazyInnerClassSingleton {

//默认使用LazyInnerClassGeneral的时候,会先初始化内部类
//如果没使用的话,内部类是不加载的
private LazyInnerClassSingleton(){
    if(LazyHolder.LAZY != null){
        throw new RuntimeException("不允许创建多个实例");
    }
}

//每一个关键字都不是多余的
//static 是为了使单例的空间共享
//保证这个方法不会被重写,重载
public static final LazyInnerClassSingleton getInstance(){
    //在返回结果以前,一定会先加载内部类
    return LazyHolder.LAZY;
}

//默认不加载
private static class LazyHolder{
    private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}

}
内部类单例调用示例:
public class LazyInnerClassSingletonTest {

public static void main(String[] args) {
    try{
        //很无聊的情况下,进行破坏
        Class<?> clazz = LazyInnerClassSingleton.class;

        //通过反射拿到私有的构造方法
        Constructor c = clazz.getDeclaredConstructor(null);
        //强制访问
        c.setAccessible(true);
        //暴力初始化
        Object o1 = c.newInstance();
        //调用了两次构造方法,相当于new了两次
        //犯了原则性问题,
        Object o2 = c.newInstance();
        System.out.println(o1 == o2);
    }catch (Exception e){
        e.printStackTrace();
    }
}

}
3、反射破坏单例解决方案及原理分析

 public class LazyInnerClassSingletonTest {
                          public static void main(String[] args) {
                                                 try{
                                                            Class<?> clazz = LazyInnerClassSingleton.class;
                                                            //通过反射拿到私有的构造方法
                                                            Constructor c = clazz.getDeclaredConstructor(null);
                                                            //强制访问,设置为true反射就可以强制访问构造方法了
                                                            c.setAccessible(true);
                                                            //暴力反射调用构造,初始化单例对象
                                                            Object o1 = c.newInstance();
                                                            //调用了两次构造方法,相当于 new 了两次
                                                            Object o2 = c.newInstance();

                                                            //比较两次出来的对象是否内存地址一致,显然是不等的。false
                                                            System.out.println(o1 == o2);
                                                       }catch (Exception e){ 
                                                                  e.printStackTrace();
                                                       }
         }

       

      注:if(null != lazy){ throw new RuntimeException("不要乱搞,别以为不知道你在用反射");}这段代码就是为了防止以上反射去破坏单例环境

4、序列化破坏单例的原理及解决方案

   import java.io.Serializable;
   public class SeriableSingleton implements Serializable {
            public final static SeriableSingleton INSTANCE = new SeriableSingleton();
            private SeriableSingleton(){}
            public static SeriableSingleton getInstance(){
                   return INSTANCE;
            }

           //以下代码为了防止序列化破坏单例(去掉一下代码序列化就可以成功破坏单例环境),为什么?我们接下来一起分析一下
           private Object readResolve(){
                 return INSTANCE;
           }
   }

 序列化破坏原理分析

      首先看一下序列化破坏的java实现代码

      public class SeriableSingletonTest {

                        public static void main(String[] args) {
                                 SeriableSingleton s1 = null;
                                 SeriableSingleton s2 = SeriableSingleton.getInstance();
                                 FileOutputStream fos = null;
                                 try {
                                                fos = new FileOutputStream("SeriableSingleton.obj");
                                               ObjectOutputStream oos = new ObjectOutputStream(fos);
                                               oos.writeObject(s2);//A代码
                                               oos.flush();
                                               oos.close();
                                               FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
                                               ObjectInputStream ois = new ObjectInputStream(fis);
                                               s1 = (SeriableSingleton)ois.readObject();//B代码
                                               ois.close();
                                              System.out.println(s1);
                                              System.out.println(s2);
                                              System.out.println(s1 == s2);
                                    } catch (Exception e) {
                                              e.printStackTrace();
                                    }
                               }
                         }

           以上是序列化破坏单例的实例教程,调用结果 false

           运行结果中,可以看出,反序列化后的对象和手动创建的对象是不一致的,实例化了两 次,违背了单例的设计初衷。那么,我们如何保证序列化的情况下也能够实现单例?其 实很简单,只需要增加 readResolve()方法即可。

           我 们 一 起 来 看 看 JDK 的 源 码 实 现 以 一 清 二 楚 了 。 我 们 进 入 ObjectInputStream 类的 readObject()方法,代码如下:

//一下是jdk源码  

public final Object readObject()

throws IOException, ClassNotFoundException

{

if (enableOverride) {
    return readObjectOverride();
}

// if nested read, passHandle contains handle of enclosing object
int outerHandle = passHandle;
try {
    Object obj = readObject0(false);
    handles.markDependency(outerHandle, passHandle);
    ClassNotFoundException ex = handles.lookupException(passHandle);
    if (ex != null) {
        throw ex;
    }
    if (depth == 0) {
        vlist.doCallbacks();
    }
    return obj;
} finally {
    passHandle = outerHandle;
    if (closed && depth == 0) {
        clear();
    }
}

}
我们发现在readObject中又调用了我们重写的readObject0()方法。进入readObject0() 方法源码,代码如下:
private Object readObject0(boolean unshared) throws IOException {

boolean oldMode = bin.getBlockDataMode();
if (oldMode) {
    int remain = bin.currentBlockRemaining();
    if (remain > 0) {
        throw new OptionalDataException(remain);
    } else if (defaultDataEnd) {
        throw new OptionalDataException(true);
    }
    bin.setBlockDataMode(false);
}
byte tc;
while ((tc = bin.peekByte()) == TC_RESET) {
    bin.readByte();
    handleReset();
}

//代码效果参考:http://www.zidongmutanji.com/zsjx/124353.html

depth++;
totalObjectRefs++;
try {
    switch (tc) {
        ......
        //这里就是为什么反序列可以控制单例,只要实现readResolve()方法
        case TC_OBJECT:
            return checkResolve(readOrdinaryObject(unshared));
        ......

        default:
            throw new StreamCorruptedException(
                String.format("invalid type code: %02X", tc));
    }
} finally {
    depth--;
    bin.setBlockDataMode(oldMode);
}

}
我们看到 TC_OBJECTD 中判断,调用了 ObjectInputStream 的 readOrdinaryObject() 方法,我们继续进入看源码: private Object readOrdinaryObject(boolean
private Object readOrdinaryObject(boolean unshared)

throws IOException

{

if (bin.readByte() != TC_OBJECT) {
    throw new InternalError();
}

ObjectStreamClass desc = readClassDesc(false);
desc.checkDeserialize();

Class<?> cl = desc.forClass();
if (cl == String.class || cl == Class.class
        || cl == ObjectStreamClass.class) {
    throw new InvalidClassException("invalid class descriptor");
}

Object obj;
try {
    //这里调用点进去看看
    obj = desc.isInstantiable() ? desc.newInstance() : null;
    
             



              代码非常简单,就是判断一下构造方法是否为空,构造方法不为空就返回 true。意味着只要有无参构造方法就会实例化。

              这时候,其实还没有找到为什么加上 readResolve()方法就避免了单例被破坏的真正原因。

} catch (Exception ex) {
    throw (IOException) new InvalidClassException(
        desc.forClass().getName(),
        "unable to create instance").initCause(ex);
}

 。。。。。。

return obj;

}
//代码效果参考:http://www.zidongmutanji.com/bxxx/213352.html

我再回到 ObjectInputStream 的 readOrdinaryObject()方法继续往下看:
private Object readOrdinaryObject(boolean unshared)

throws IOException

{

 。。。。。。

if (obj != null &&
    handles.lookupException(passHandle) == null &&
    //判断无参构造方法是否存在之后,又调用了 hasReadResolveMethod()方法,来看代码:
    desc.hasReadResolveMethod())
    进入代码看看如下:
   
   逻辑非常简单,就是判断 readResolveMethod 是否为空,不为空就返回 true。那么 readResolveMethod 是在哪里赋值的呢?通过全局查找找到了赋值代码在私有方法 ObjectStreamClass()方法中给 readResolveMethod 进行赋值,来看代码:
    
   上面的逻辑其实就是通过反射找到一个无参的 readResolve()方法,并且保存下来。现在 再 回 到 ObjectInputStream 的 readOrdinaryObject() 方 法 继 续 往 下 看 , 如 果 readResolve()存在则调用 invokeReadResolve()方法,来看代码:
    


             我们可以看到在 invokeReadResolve()方法中用反射调用了 readResolveMethod 方法。

             通过 JDK 源码分析我们可以看出,虽然,增加 readResolve()方法返回实例,解决了单 例被破坏的问题。

             但是,我们通过分析源码以及调试,我们可以看到实际上实例化了两 次,只不过新创建的对象没有被返回而已。

             那如果,创建对象的动作发生频率增大,就 意味着内存分配开销也就随之增大

{
    Object rep = desc.invokeReadResolve(obj);
    if (unshared && rep.getClass().isArray()) {
        rep = cloneArray(rep);
    }
  。。。。。。

return obj;

}
5、单例总结

单例的实现几种形式:饿汉式、懒汉式、内部类、枚举、容器、ThreadLocal等六种形式(当然还有其他形式大家可以自行了解)。

破坏单例的常见方式:序列化、java反射。掌握以上哪些单例形式可以被反射或序列化破坏,以及如何才能防止这两种方式的破坏原理。
相关文章
|
6天前
|
设计模式 安全 Java
Java编程中的单例模式:理解与实践
【10月更文挑战第31天】在Java的世界里,单例模式是一种优雅的解决方案,它确保一个类只有一个实例,并提供一个全局访问点。本文将深入探讨单例模式的实现方式、使用场景及其优缺点,同时提供代码示例以加深理解。无论你是Java新手还是有经验的开发者,掌握单例模式都将是你技能库中的宝贵财富。
12 2
|
24天前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
27 2
|
16天前
|
设计模式 安全 Java
Java编程中的单例模式深入剖析
【10月更文挑战第21天】在Java的世界里,单例模式是设计模式中一个常见而又强大的存在。它确保了一个类只有一个实例,并提供一个全局访问点。本文将深入探讨如何正确实现单例模式,包括常见的实现方式、优缺点分析以及最佳实践,同时也会通过实际代码示例来加深理解。无论你是Java新手还是资深开发者,这篇文章都将为你提供宝贵的见解和技巧。
92 65
|
5天前
|
设计模式 安全 Java
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
Kotlin教程笔记(57) - 改良设计模式 - 单例模式
|
6天前
|
设计模式 安全 Java
Java编程中的单例模式深入解析
【10月更文挑战第31天】在编程世界中,设计模式就像是建筑中的蓝图,它们定义了解决常见问题的最佳实践。本文将通过浅显易懂的语言带你深入了解Java中广泛应用的单例模式,并展示如何实现它。
|
13天前
|
设计模式 存储 数据库连接
PHP中的设计模式:单例模式的深入理解与应用
【10月更文挑战第22天】 在软件开发中,设计模式是解决特定问题的通用解决方案。本文将通过通俗易懂的语言和实例,深入探讨PHP中单例模式的概念、实现方法及其在实际开发中的应用,帮助读者更好地理解和运用这一重要的设计模式。
13 1
|
17天前
|
设计模式 Java 程序员
[Java]23种设计模式
本文介绍了设计模式的概念及其七大原则,强调了设计模式在提高代码重用性、可读性、可扩展性和可靠性方面的作用。文章还简要概述了23种设计模式,并提供了进一步学习的资源链接。
33 0
[Java]23种设计模式
|
1天前
|
设计模式 JavaScript Java
Java设计模式:建造者模式详解
建造者模式是一种创建型设计模式,通过将复杂对象的构建过程与表示分离,使得相同的构建过程可以创建不同的表示。本文详细介绍了建造者模式的原理、背景、应用场景及实际Demo,帮助读者更好地理解和应用这一模式。
|
29天前
|
设计模式 存储 数据库连接
PHP中的设计模式:单例模式的深入解析与实践
在PHP开发中,设计模式是提高代码可维护性、扩展性和复用性的关键技术之一。本文将通过探讨单例模式,一种最常用的设计模式,来揭示其在PHP中的应用及优势。单例模式确保一个类仅有一个实例,并提供一个全局访问点。通过实际案例,我们将展示如何在PHP项目中有效实现单例模式,以及如何利用这一模式优化资源配置和管理。无论是PHP初学者还是经验丰富的开发者,都能从本文中获得有价值的见解和技巧,进而提升自己的编程实践。
|
26天前
|
设计模式 安全 Java
C# 一分钟浅谈:设计模式之单例模式
【10月更文挑战第9天】单例模式是软件开发中最常用的设计模式之一,旨在确保一个类只有一个实例,并提供一个全局访问点。本文介绍了单例模式的基本概念、实现方式(包括饿汉式、懒汉式和使用 `Lazy&lt;T&gt;` 的方法)、常见问题(如多线程和序列化问题)及其解决方案,并通过代码示例详细说明了这些内容。希望本文能帮助你在实际开发中更好地应用单例模式,提高代码质量和可维护性。
29 1
下一篇
无影云桌面