八大设计模式——单例模式

八大设计模式——单例模式

单例模式:

单例模式是指在内存中只会仅创建一次对象的设计模式,这是为了防止一个程序中对同一对象的多次创建,导致内存浪费的问题,单例模式就是让所需要调用的地方都共享这一单例对象。在单例模式中针对不同的业务一般有四种创建模式。

1、懒汉式:

在需要使用的时候才去创建该单例对象,可以理解为石器时代,工具只有在被需要的时候,原始人才会去发明制造。

使用场景:适用于需要延迟初始化的情况,可以减少程序启动时的资源消耗。不适用于多线程环境下,需要考虑线程安全性。

//懒汉式
public class LazySingleton {
    private static LazySingleton instance;
	private LazySingleton() {
    	// 私有构造函数
	}

	public static LazySingleton getInstance() {
    	if (instance == null) {
        	instance = new LazySingleton();
    	}
    	return instance;
		}
    }
双重检索懒汉式(升级版的懒汉式)

使用场景:适用于需要保证线程安全且需要延迟初始化的情况。一般情况下,推荐使用这种方式。

//双重检索懒汉式
//这种模式即保证了线程安全,又提高了性能。
public class LazySington {
    //valatile关键字是用来防止指令重排序
    private volatile  static LazySington lazySington;
    private LazySington(){
        System.out.println("---instance LazySington---");
    }
    //双重检索,是为了保证线程安全
    public static LazySington newInstance(){
        // 第一次检查,避免不必要的加锁
        if (lazySington == null){
            synchronized (RuntimeException.class){
                // 第二次检查,确保只创建一个实例
                if (lazySington == null){
                    lazySington = new LazySington();
                }
            }
        }
        return lazySington;
    }
}
class Test {
    public static void main(String[] args) {
        LazySington lazy1=  LazySington.newInstance();
        LazySington lazy2 = LazySington.newInstance();
        System.out.println(lazy2 == lazy1);
    }
}
双重检索:

问题:当两个线程恰巧同一时刻去判空,发现为空后,又同一时间去创建对象,那这个时候就不是单例了,这就变成了多例。那该如何解决?

首先线程问题第一个想到的就是给类对象加同步锁synchronized,但是这里会发现每次获取对象的时候都要先去获取锁,无疑是增大了链路开销,所以要对其进行一个优化,可以试想一下可以再加一个判断若没有实例化就直接加锁,若已经实例化就不需要获取锁,直接获取实例,这种方式叫做双重校验锁。

指令重排问题:

创建对象时JVM会有以下三步:1、分配空间 2、初始化单例对象 3、将单例指向分配好的内存空间。

指令重排是因为JVM在保证队中结果的情况下,并不会按照程序编码顺序执行,这样做也是为尽可能提高程序性能。主要是2,3步会发生指令颠倒情况,若颠倒之后,我们获取到未初始化的单例对象,可能会空指针异常。这个时候我们的关键字volatile关键字就登场了,把它加载私有属性上, 通过使用Volatile关键字,防止指令重排序。

2、饿汉式:

概念:在类加载时候已经创建好了,等待被程序使用。就相当于现代社会各种人才在大学的时候被培养,然后毕业在放到社会市场等待被挑选。需要时才被调用,不存在线程安全问题。

使用场景:适用于实例化对象的成本较低,且需要确保线程安全的情况。不适用于需要演示初始化的情况。

类在加载时会在堆内创建一个单例对象,当类被卸载,单例对象也随之消亡。

//饿汉式
//优点:首先方便调用,其次因为没有加锁,因此执行效率会比较高。
//缺点:在类加载的时候就创建了,若不使用,是不是就造成了空间浪费。
public class HuggerSingleton {
    private static HuggerSingleton reader = new HuggerSingleton();

    private HuggerSingleton(){
        System.out.println("---instance HuggerSingleton---");
    }
    public  static HuggerSingleton newInstance(){
        return reader;
    }
}
class test{
    public static void main(String[] args) {
        HuggerSingleton b1 = HuggerSingleton.newInstance();
        HuggerSingleton b2 = HuggerSingleton.newInstance();
        System.out.println(b1==b2);
    }
}

总结:使用懒汉式或是饿汉式他都会存在反射和序列化,它两都可以把单例对象破坏掉(产生多个对象)。

反射模式访问了类的私有构造器,创建了另一个对象。

3、静态内部类:

就是把对象实例化放在静态方法类中,这样会保证线程安全,同时也不需要同步锁来获取对象。

使用场景:适用于需要保证线程安全且需要延迟初始化的情况。在某些情况下它比双重检查锁方式更具优势,因为不需要显式的同步。

//静态内部类
public class StaticSingleton {
    private static class SSingleton {
        private static final StaticSingleton SINGLETON = new StaticSingleton();
    }
    public static StaticSingleton newInstance(){
        return SSingleton.SINGLETON;
    }
}

4、枚举类:

枚举类时通过定义一个枚举类型的单例类,通过INSTANCE来创建对象的一种单例方法。

使用场景:适用于需要保证线程安全,防止序列化攻击和反射攻击的情况。枚举类型单例是最简单和最安全的单例实现方式,大多数情况是首选。

//枚举类
public enum enumSingleton {
    INSTANCE;
    private String person;
    public String getInstance(){
        return person;
    }
}
class enumTest{
    public static void main(String[] args) {
        enumSingleton e1 =enumSingleton.INSTANCE;
        enumSingleton e2 =enumSingleton.INSTANCE;
        System.out.println(e2 == e1);
    }
}

首先它短小,只需要调用INSTANCE就可以实现。其次它不需要格外操作去保证线程安全。而且他还可以防止反射,序列化与反序列化,那么它是如何防止序列化攻击和防止反射攻击的?

防止序列化攻击:枚举类型不会被序列化破坏,是因为枚举类型的实例在反序列化时保持不变

防止反射攻击: 枚举类型单例不会被反射攻击破坏,是因为枚举类型的构造函数是私有的,无法通过反射调用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值