创建模式-单例模式(Singleton Pattern)

目的:

        一个类的实例在系统中只有一个,从而实现减少垃圾对象的产生,从而提高效率。

应用场景:

        工具类

实现方式:

        说在开头,下述的所有实现方式我们按如下思路推进。

        首先最简单的实现方式饿汉方式,但饿汉方式在类加载的时候就创建了单例对象,这样有点浪费内存;

        进而改造为懒汉模式一懒汉模式一是在第一次使用单例对象时才创建,解决了饿汉模式的缺点(浪费内存),但是懒汉模式一没有实现获取单例对象函数的线程安全性,所以有了懒汉模式二,其对获取单例对象的函数使用synchronize进行加锁,这样虽然解决了懒汉模式一的线程不安全问题,但是synchronize锁太重造成每次调用获取单例对象函数都是同步的,效率低下;

        再进而通过双检查锁解决懒汉模式二锁太重的问题,双检查锁实现的方式是只有在单例对象为null的时候才会锁,如果已经创建好了单例对象则不需要锁,双检查锁有个指令重排的问题,所以引入了关键字volatile来避免指令重排,解决单例对象的引用已经不是null,但指向的单例对象还没创建好而造成的问题,更详细的说明见代码注释。

        创建一个 Java 对象一般有 4 种方式:new 、克隆、序列化、反射,将构造函数用private修饰后,new关键字就不能用来创建对象了,那么还剩下另外三种,所以单例模式还存在三种攻击:①克隆和②序列化攻击及③反射攻击;枚举单例很好的解决了这三种攻击问题。所以目前来说枚举是单例的最好实践。

饿汉模式

package com.cld.designpattern.creation.singleton.hungry;

import java.io.Serializable;

/**
 * 饿汉式
 * 是否 Lazy 初始化:否
 * <p>
 * 是否多线程安全:是
 * <p>
 * 实现难度:易
 * <p>
 * 描述:这种方式比较常用,但容易产生垃圾对象。
 * 优点:没有加锁,执行效率会提高。
 * 缺点:类加载时就初始化,浪费内存。
 * 它基于 classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化 instance 显然没有达到 lazy loading 的效果。
 *
 * @author 休克柏
 */
public class HungrySingleton implements Serializable,Cloneable {
    private static final HungrySingleton INSTANCE = new HungrySingleton();

    private HungrySingleton() {
        if (INSTANCE != null) {
            throw new RuntimeException("单例构造器禁止反射调用!");
        }
    }

    public static HungrySingleton getInstance() {
        return INSTANCE;
    }

    @Override
    public HungrySingleton clone() {
        
        //避免克隆攻击
//        try {
//            HungrySingleton clone = (HungrySingleton) super.clone();
//            // TODO: copy mutable state here, so the clone can't change the internals of the original
//            return clone;
//        } catch (CloneNotSupportedException e) {
//            throw new AssertionError();
//        }
        return getInstance();
    }
}

懒汉模式一

import java.io.ObjectStreamException;
import java.io.Serializable;

/**
 * (线程不安全)懒汉式
 * 是否 Lazy 初始化:是
 * 
 * 是否多线程安全:否
 * 
 * 实现难度:易
 * 
 * 描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加
 * 锁synchronized,所以严格意义上它并不算单例模式。
 * 这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。
 *
 * @author TNT_LEE
 */
public class Lazy1Singleton implements Serializable {
    private static Lazy1Singleton instance;

    private Lazy1Singleton() {
    }

    /**
     * 线程不安全
     *
     * @return Lazy1Singleton
     */
    public static Lazy1Singleton getInstance() {
        if (instance == null) {
            instance = new Lazy1Singleton();
        }
        return instance;
    }

    /**
     * 反序列化的时候,会调用该方法,从而避免反序列化对单例的破坏
     * @return Lazy1Singleton
     * @throws ObjectStreamException
     */
    private Object readResolve() throws ObjectStreamException {
        return getInstance();
    }
}

懒汉模式二

import java.io.Serializable;

/**
 * (线程安全)懒汉式
 * 是否 Lazy 初始化:是
 * 
 * 是否多线程安全:是
 * 
 * 实现难度:易
 * 
 * 描述:这种方式具备很好的 lazy loading,能够在多线程中很好的工作,
 * 但是,效率很低,99% 情况下不需要同步。
 * 优点:第一次调用才初始化,避免内存浪费。
 * 缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
 * getInstance() 的性能对应用程序不是很关键(该方法使用不太频繁)。
 *
 * @author 休克柏
 */
public class Lazy2Singleton implements Serializable {
    private static Lazy2Singleton instance;

    private Lazy2Singleton() {}

    /**
     * 线程安全,但是synchronized锁比较重
     *
     * @return Lazy2Singleton
     */
    public static synchronized Lazy2Singleton getInstance() {
        if (instance == null) {
            instance = new Lazy2Singleton();
        }
        return instance;
    }
}

Double Check

/**
 * JDK 版本:JDK1.5 起
 * 
 * 是否 Lazy 初始化:是
 * 
 * 是否多线程安全:是
 * 
 * 实现难度:较复杂
 * 
 * 描述:这种方式采用双锁机制,安全且在多线程情况下能保持高性能。
 * getInstance() 的性能对应用程序很关键。
 * 
 * Lazy2Singleton相对于Lazy1Singleton的效率问题,其实是为了解决1%几率的问题,
 * 而使用了一个100%出现的防护盾。
 * 那有一个优化的思路,就是把100%出现的防护盾,也改为1%的几率出现,使之只出现在可能会导致多个实例出现的地方。
 *
 * @author 休克柏
 */
public class DclSingleton implements Serializable {
    /**
     * volatile 的作用是对dclSingleton的写操作有一个内存屏障,这样,在它的赋值完成之前,就不用会调用读操作。
     * 
     * volatile关键字通过提供“内存屏障”的方式来防止指令被重排序,为了实现volatile的内存语义,
     * 编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。
     * 
     * 在java内存模型中,volatile 关键字作用可以是保证可见性或者禁止指令重排。
     * 这里是因为 dclSingleton = new DclSingleton() ,它并非是一个原子操作,事实上:
     * 在 JVM 中上述语句至少做了以下这 3 件事:
     * 
     * 第一步是给 dclSingleton 分配内存空间;
     * 
     * 第二步开始调用 DclSingleton 的构造函数等,来初始化 dclSingleton;
     * 
     * 第三步,将 dclSingleton 对象指向分配的内存空间(执行完这步 dclSingleton 就不是 null 了)。
     * 
     * 这里需要留意一下 1-2-3 的顺序,因为存在指令重排序的优化,也就是说第 2 步和第 3 步的顺序是
     * 不能保证的,最终的执行顺序,可能是 1-2-3,也有可能是 1-3-2。
     * 如果是 1-3-2,那么在第 3 步执行完以后,dclSingleton 就不是 null 了,可是这时第 2 步并没
     * 有执行,singleton 对象未完成初始化,它的属性的值可能不是我们所预期的值。
     * 假设此时线程 2 进入 getInstance 方法,由于 dclSingleton 已经不是 null 了,
     * 
     * 所以会通过第一重检查并直接返回,但其实这时的 singleton 并没有完成初始化,所以使用这个实例的时候会报错。
     */
    private volatile static DclSingleton dclSingleton;

    private DclSingleton() {}

    public static DclSingleton getDlcSingleton() {
        if (dclSingleton == null) {
            synchronized (DclSingleton.class) {
                if (dclSingleton == null) {
                    //1. 给 dclSingleton 分配内存
                    //2. 调用 dclSingleton 的构造函数来初始化成员变量,形成实例
                    //3. 将dclSingleton对象指向分配的内存空间(执行完这步 singleton才是非 null了)

                    //上述3步是dclSingleton = new DclSingleton()的指令执行顺序,
                    dclSingleton = new DclSingleton();
                }
            }
        }
        return dclSingleton;
    }

    /**
     * 反序列化的时候,会调用该方法,从而避免反序列化对单例的破坏
     * @return Lazy1Singleton
     * @throws ObjectStreamException
     */
    private Object readResolve() throws ObjectStreamException {
        return getDlcSingleton();
    }
}

枚举方式

import java.io.Serializable;

/**
 * 1)需要额外的工作来实现序列化,否则每次反序列化一个序列化的对象时都会创建一个新的实例。
 * <p>
 * 2)可以使用反射强行调用私有构造器(如果要避免这种情况,可以修改构造器,让它在创建第二个实例的时候抛异常)。
 * <p>
 * 而枚举类很好的解决了这两个问题,使用枚举除了线程安全和防止反射调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。
 * 因此,《Effective Java》作者推荐使用的方法。不过,在实际工作中,很少看见有人这么写。
 * 解决了反射攻击和反序列化攻击:
 * 1-防止序列化:通过name和class类型获取枚举常量,因此枚举中的name是唯一的,对应一个枚举常量。
 * 所以序列化和反序列化对枚举破坏没有影响。
 * 2-反射攻击:Enum没有无参构造器,只有一个有参构造器。反射攻击无效,源码中会判断是不是枚举类型,是的话抛出非法参数异常,不能反射创建枚举对象。
 * <p>
 * https://blog.csdn.net/hskw444273663/article/details/85223491
 * In software engineering, a software design pattern is a general, reusable solution to a commonly occurring problem within a given context in software design.
 * <p>
 * It is not a finished design that can be transformed directly into source or machine code.
 * <p>
 * Rather, it is a description or template for how to solve a problem that can be used in many different situations.
 * <p>
 * Design patterns are formalized best practices that the programmer can use to solve common problems when designing an application or system.
 *
 * @author TNT_LEE
 */
public enum EnumSingleton implements Serializable {
    /**
     * 实例
     */
    INSTANCE;

    public void whateverMethod() {}

}

攻击方式

        创建一个 Java 对象一般有 4 种方式:new 、克隆、序列化、反射,将构造函数用private修饰后,new关键字就不能用来创建对象了,那么还剩下另外三种,所以单例模式还存在三种攻击:①克隆攻击和②序列化攻击及③反射攻击;枚举单例很好的解决了这三种攻击问题。所以目前来说枚举是单例的最好实践。

克隆攻击

        java提供了一个Cloneable接口来标示这个对象是可拷贝的。当一个类实现了Cloneable接口后,就表明该类从Object继承的clone()方法合法。就可以通过调用改方法返回一个Object对象,该对象的构建过程不需要调用构造函数,因为对象的构建过程是直接从内存中进行的二进制拷贝,对一些需要深拷贝的成员需要程序员做额外的处理。所以避免克隆攻击的方法就是不要实现Clone接口,如果实现了就覆盖clone()方法。java的原型模式实战就是通过上述逻辑来实现的。

package com.cld.designpattern.creation.singleton.attack.cloneattack;

public class CloneAttackDemo implements Cloneable{

    private static final CloneAttackDemo INSTANCE = new CloneAttackDemo();

    private CloneAttackDemo() {
        if (INSTANCE != null) {
            throw new RuntimeException("单例构造器禁止反射调用!");
        }
    }

    public static CloneAttackDemo getInstance() {
        return INSTANCE;
    }
    /**
     * Creates and returns a copy of this object.  The precise meaning
     * of "copy" may depend on the class of the object. The general
     * intent is that, for any object {@code x}, the expression:
     * <blockquote>
     * <pre>
     * x.clone() != x</pre></blockquote>
     * will be true, and that the expression:
     * <blockquote>
     * <pre>
     * x.clone().getClass() == x.getClass()</pre></blockquote>
     * will be {@code true}, but these are not absolute requirements.
     * While it is typically the case that:
     * <blockquote>
     * <pre>
     * x.clone().equals(x)</pre></blockquote>
     * will be {@code true}, this is not an absolute requirement.
     * <p>
     * By convention, the returned object should be obtained by calling
     * {@code super.clone}.  If a class and all of its superclasses (except
     * {@code Object}) obey this convention, it will be the case that
     * {@code x.clone().getClass() == x.getClass()}.
     * <p>
     * By convention, the object returned by this method should be independent
     * of this object (which is being cloned).  To achieve this independence,
     * it may be necessary to modify one or more fields of the object returned
     * by {@code super.clone} before returning it.  Typically, this means
     * copying any mutable objects that comprise the internal "deep structure"
     * of the object being cloned and replacing the references to these
     * objects with references to the copies.  If a class contains only
     * primitive fields or references to immutable objects, then it is usually
     * the case that no fields in the object returned by {@code super.clone}
     * need to be modified.
     * <p>
     * The method {@code clone} for class {@code Object} performs a
     * specific cloning operation. First, if the class of this object does
     * not implement the interface {@code Cloneable}, then a
     * {@code CloneNotSupportedException} is thrown. Note that all arrays
     * are considered to implement the interface {@code Cloneable} and that
     * the return type of the {@code clone} method of an array type {@code T[]}
     * is {@code T[]} where T is any reference or primitive type.
     * Otherwise, this method creates a new instance of the class of this
     * object and initializes all its fields with exactly the contents of
     * the corresponding fields of this object, as if by assignment; the
     * contents of the fields are not themselves cloned. Thus, this method
     * performs a "shallow copy" of this object, not a "deep copy" operation.
     * <p>
     * The class {@code Object} does not itself implement the interface
     * {@code Cloneable}, so calling the {@code clone} method on an object
     * whose class is {@code Object} will result in throwing an
     * exception at run time.
     *
     * @return a clone of this instance.
     * @throws CloneNotSupportedException if the object's class does not
     *                                    support the {@code Cloneable} interface. Subclasses
     *                                    that override the {@code clone} method can also
     *                                    throw this exception to indicate that an instance cannot
     *                                    be cloned.
     * @see Cloneable
     */
    @Override
    protected CloneAttackDemo clone() throws CloneNotSupportedException {
        try {
            // TODO: copy mutable state here, so the clone can't change the internals of the original
            return (CloneAttackDemo) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        CloneAttackDemo original = CloneAttackDemo.getInstance();
        CloneAttackDemo clonedFromOriginal = original.clone();
        if(original != clonedFromOriginal){
            System.out.println("克隆攻击成功");
        }else{
            System.out.println("克隆攻击失败");
        }

    }
}

序列化攻击

序列化攻击参考:单例模式安全之序列化攻击 - 简书单例模式安全之序列化攻击 源码 什么是序列化攻击呢? 简单说,一个单例对象经过序列化再反序列化后,内存中会存在两个对象,这样单例模式就被破坏。 序列化攻击复现 序列化攻击复过...https://www.jianshu.com/p/7017ead4808c

JAVA对象流序列化时的readObject,writeObject,readResolve是怎么被调用的_supermanL的博客-CSDN博客_readobject有时候,我们会在很多涉及到通过JAVA对象流进行序列化和反序列化时,会看到下面的方法:private void writeObject(java.io.ObjectOutputStream s)throws java.io.IOExceptionprivate void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException以及我们在写我们的单例类时,如果使用的不是枚举的实现https://blog.csdn.net/u014653197/article/details/78114041

反射攻击

Reflection is commonly used by programs which require the ability to examine or modify the runtime behavior of applications running in the Java virtual machine. This is a relatively advanced feature and should be used only by developers who have a strong grasp of the fundamentals of the language. With that caveat(警告; 告诫;) in mind, reflection is a powerful technique and can enable applications to perform operations which would otherwise be impossible.

/**
 * 饿汉单例模式,通过修改私有构造函数可以解决反射攻击,但是对懒汉单例模式无效
 * 饿汉单例模式对象是静态初始化的,不需要再次使用构造函数,所以可以对只要使用到构造函数就报错处理。
 */
@Slf4j
public class ReflectionAttackDemo {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<HungrySingleton> object = HungrySingleton.class;
        Constructor<HungrySingleton> constructor = object.getDeclaredConstructor();
        constructor.setAccessible(true);

        HungrySingleton instance = HungrySingleton.getInstance();
        HungrySingleton newInstance = constructor.newInstance();

        System.out.println(instance);
        System.out.println(newInstance);
        if (instance == newInstance) {
            log.info("反射攻击成功!");
        } else {
            log.info("反射攻击失败!");
        }
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
SimpAutoUpdater c#自动升级 模块源码 可以集成到自己程序: 首先在VS中为当前的主程序项目添加引用,引用“客户端”中的“SimpleUpdater.exe”。 在VS中,点开“解决方案管理器”中相应项目的“属性”节点,打开 AssemblyInfo.cs 文件,在最下面添加上一行自动更新声明: //--添加这行标记表示支持自动更新, 后面的网址为自动更新的根目录. [assembly: FSLib.App.SimpleUpdater.Updateable("http://ls.com/update.xml")] 这步是必须的,否则请求检查更新时会抛出异常;代码中的网址即上面提到的能访问到xml文件的网址。 如果您希望更加简单的使用而不用去加这样的属性,或者您想程序运行的时候自定义,您可以通过下列方式的任何一种方式取代上面的属性声明: 使用 FSLib.App.SimpleUpdater.Updater.CheckUpdateSimple("升级网址") 的重载方法。这个重载方法允许你传入一个升级包的地址; 在检查前手动设置 FSLib.App.SimpleUpdater.Updater.UpdateUrl 属性。这是一个静态属性,也就是说,您并不需要创建 FSLib.App.SimpleUpdater.Updater.UpdateUrl 的对象实例就可以修改它。 无论使用哪种方式,请确保在检查更新前,地址已经设置。 到这里,准备工作即告完成,为代码添加上检查更新的操作即可。 static class Program { /// /// 应用程序的主入口点。 /// [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); var updater = FSLib.App.SimpleUpdater.Updater.Instance; //当检查发生错误时,这个事件会触发 updater.Error += new EventHandler(updater_Error); //没有找到更新的事件 updater.NoUpdatesFound += new EventHandler(updater_NoUpdatesFound); //找到更新的事件.但在此实例中,找到更新会自动进行处理,所以这里并不需要操作 //updater.UpdatesFound += new EventHandler(updater_UpdatesFound); //开始检查更新-这是最简单的模式.请现在 assemblyInfo.cs 中配置更新地址,参见对应的文件. FSLib.App.SimpleUpdater.Updater.CheckUpdateSimple(); /* * 如果您希望更加简单的使用而不用去加这样的属性,或者您想程序运行的时候自定义,您可以通过下列方式的任何一种方式取代上面的属性声明: * 使用Updater.CheckUpdateSimple 的重载方法。这个重载方法允许你传入一个升级包的地址; * 在检查前手动设置 FSLib.App.SimpleUpdater.Updater.UpdateUrl 属性。这是一个静态属性,也就是说,您并不需要创建 FSLib.App.SimpleUpdater.Updater.UpdateUrl 的对象实例就可以修改它。 */ FSLib.App.SimpleUpdater.Updater.CheckUpdateSimple("升级网址"); Application.Run(new Form1()); } static void updater_UpdatesFound(object sender, EventArgs e) { } static void updater_NoUpdatesFound(object sender, EventArgs e) { System.Windows.Forms.MessageBox.Show("没有找到更新"); } static void updater_Error(object sender, EventArgs e) { var updater = sender as FSLib.App.SimpleUpdater.Updater; System.Windows.Forms.MessageBox.Show(updater.Exception.ToString()); } }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值