设计模式——Singleton(单例)模式

意图

保证系统中一个类只有一个实例并且提供对外访问。

优点

由于在内存中只存在一个对象,故可以节约系统资源,对于一些需要频繁创建和销毁的的对象,单例模式无疑可以提高系统的性能。

缺点

由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。多用于web应用设计以及各种池的设计。

实现要点

构造函数私有化。类定义中含有一个该类的静态私有对象,以及一个暴露单例的静态方法。

饿汉模式

//饿汉模式
public class Singleton1 {
	//类一加载,就创建对象,故称为饿汉模式
    private static Singleton1 singleton = new Singleton1();

    private Singleton1(){
        System.out.println("生成了一个实例");
    }

    public static Singleton1 getInstance() {
        return singleton;
    }
}

将对象放在类成员变量中,这样就可以在加载时初始化实例

优点:线程安全(JVM保证在多个线程想要加载类时,只有一个线程能加载)。

缺点:创建一些暂时不需要使用的单例,耗费资源,启动速度慢。

懒汉模式

//懒汉模式
public class Singleton2 {
	//不初始化,等到要用时再初始化,称为延迟加载
    private static Singleton2 singleton2;
    private Singleton2(){
        System.out.println("生成了一个实例");
    }
	
	//有线程安全问题,需要用sychronized进行线程同步
    public static synchronized Singleton2 getInstance() {
    	//线程不安全,因为有可能两个线程同时访问
    	//在第一个线程正在创建对象时,第二个线程也进入if然后创建对象
        if (singleton2 == null) {
            singleton2 = new Singleton2();
        }
        return singleton2;
    }
}

将创建的工作暴露在方法中
优点:延迟加载,资源利用率高
缺点:线程不安全

双重检测锁

为了实现线程安全可采用如下方式:

public class safeLazyInitialization {
    private static safeLazyInitialization instance;
    
    public synchronized static safeLazyInitialization getInstance() {
        if (instance == null) {
            instance = new safeLazyInitialization();
        }
        return instance;
    }
}

上述方案是线程安全的,但是由于锁住了整个getInstance(),所以这种单例模式的效率是非常低下的。尽量缩小锁的范围。所以我们不锁整个方法,而是锁住方法中的部分代码。

再次改进代码

public class safeDoubleCheckLocking {
    private static safeDoubleCheckLocking instance;
    public static safeDoubleCheckLocking getInstance() {
        if (instance == null) {
            synchronized (safeDoubleCheckLocking.class) {
                if (instance == null) {
                    instance = new safeDoubleCheckLocking();
                }
            }
        }
        return instance;
    }
}

相比于前一种,这种实现明显效率更高,因为synchronized关键字外部有一层判断,只要实例被创建了,就不会再进入同步代码块,而前一种是每次调用方法都会进入同步代码。

为什么synchronized外部加了一次判断,内部还要加一次判断呢?

double-check,也就是常说的双重校验。假设线程A执行到了第6行(尚未执行)if (instance == null),此时instance依然等于null,而线程B可能已经进入了外层判断,而且被阻塞在synchronized这里。线程A继续执行完成对象的创建后释放锁,线程B获取锁进入同步代码块,如果没有第二次判断,线程B会直接创建对象。所以synchronized内也必须加一次判断。

线程不安全的根本原因就是instance = new safeDoubleCheckLocking()不是原子操作。而是分为三步完成
1、分配内存给这个对象
2、初始化这个对象
3、把instance变量指向初始化的对象
正常情况下按照1 -> 2 -> 3的顺序执行,但是2和3可能会发生重排序,执行顺序变成1 -> 3 -> 2。如果是1 -> 3 -> 2的顺序执行。线程A执行完3,此时对象尚未初始化,但是instance变量已经不为null,线程B执行到synchronized关键字外部的if判断时,就直接返回了。此时线程B拿到的是一个尚未初始化完成的对象,可能会造成安全隐患。所以这种实现方式是线程不安全的。要解决这个问题,也就是解决重排序的问题,应该想到了另一个关键字volatile。

再次改造代码:

public class safeDoubleCheckLocking {
    private volatile static safeDoubleCheckLocking instance;

    public static safeDoubleCheckLocking getInstance() {
        if (instance == null) {
            synchronized (safeDoubleCheckLocking.class) {
                if (instance == null) {
                    instance = new safeDoubleCheckLocking();
                }
            }
        }
        return instance;
    }
}

静态内部类实现

双重校验锁的实现方式涉及到的知识较多,所以相对来说,还有更加简便的方式,那就是利用静态内部类


public class staticInnerClass {
    private staticInnerClass instance;
    private staticInnerClass(){
        
    }
    
    public static staticInnerClass getInstance() {
        return SingletonInstance.instance;
    }
    
    private static class SingletonInstance {
        private static staticInnerClass instance = new staticInnerClass();
    }
}

这种实现方式既满足懒加载,又满足线程安全,代码量还少,相对来说是一种比较优雅的实现方式。

枚举方式

class Singleton{}
public enum A{
	private Singleton instance;
	A(){
		instance=new Singleton();
	}

	public Singleton getInstance(){
		return instance;
	}
}

优点:实现简单,避免反射和序列化的漏洞

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值