【设计模式】创建型:单例模式(Singleton Pattern)

什么是单例模式

单例,就是单个对象单个实例的意思,是指在系统运行期间,某个对象类型最多只会创建出一个实例,创建和管理单例对象的方法就是单例模式。

选择使用单例对象的原因,通常都是因为创建这种对象的成本比较高,要么是很费时间,要么是很费资源。所以,就创建一个这样的对象,反复使用它就好了,能节约很多时间和资源。

通常,单例对象的生命周期都会比一般对象要长,一旦创建好了就常驻内存,随用随取。

单例对象可以有作用域的概念,比如,限定一个进程内只能有一个单例,或者一个线程内只能有一个单例,也可以将作用域和HTTP的生命周期绑定,限定在一个完整的HTTP请求和响应过程内只能有一个单例。还可以有其它作用域定义,当然,这需要额外的手段才能实现。

使用单例模式,通常要考虑三方面的问题:

  1. 单例对象的创建时机,是早点创建,还是晚点创建,这个没有定论,要结合实际自主选择。
  2. 单例对象在创建过程中的线程安全性,核心就是要保证单例唯一性,避免创建出多个对象。
  3. 单例对象在使用过程中的线程安全性,这是针对单例对象操作的资源来说,要保证资源的并发安全性。

单例模式的实现

饿汉模式

饿了就想赶快吃东西,表达含义就是要尽早创建单例对象,通常做法是在系统启动过程中就创建好。

模式特点:

  1. 首次使用单例对象时无需等待创建,可以提高首次使用效率。
  2. 通常不需要考虑创建过程的线程安全性,因为较早阶段没有使用者,且创建者通常是不会并发操作的。
  3. 可能会因为单例对象创建耗时,拖慢系统的启动速度。

代码示例,在类加载的时候创建单例对象:

public class Singleton {
	private static final Singleton singleton = new Singleton();

    private Singleton() {
    }

    /**
     * 获取单例对象的静态方法
     *
     * @return 单例对象
     */
    public static Singleton getInstance() {
        return singleton;
    }
}

这段代码里,通过static final关键字,其实也能保证单例创建过程的线程安全性。

懒汉模式

懒人凡事都会往后拖,表达含义就是要延迟对象创建时机,通常做法是延迟到首次使用时再创建。

模式特点:

  1. 首次使用时多了创建对象的步骤,所以首次使用对象的效率会降低。
  2. 创建过程和使用过程都要考虑线程的并发安全性。
  3. 系统启动速度不会被拖累。

代码示例:在首次使用的时候创建单例对象:

public class Singleton {
    private static Singleton singleton;
    
    private Singleton() {
    }

    /**
     * 获取单例对象的静态方法
     * 注意!这里的创建过程是线程不安全的!!!
     *
     * @return 单例对象
     */
    public static Singleton getInstance() {
    	// 如果没有单例对象,就先创建再使用
        if (singleton == null) {
            singleton = new Singleton();
        }
        return singleton;
    }
}

注意,这里的创建过程没有考虑并发性,是线程不安全的!

创建过程的线程安全性

在饿汉模式中,可以通过static final关键字保证单例的创建安全性,上文已有演示:

private static final Singleton singleton = new Singleton();

在懒汉模式中,有以下三种经典方式可以参考:

方式一,用synchronized关键字对单例创建方法加锁:

public static synchronized Singleton getInstance() {
	if (singleton == null) {
		singleton = new Singleton();
	}
	return singleton;
}

方式二,用synchronized关键字对单例创建代码块加锁,配合双重校验锁定DCL(Double Check Lock)

public static Singleton getInstance() {
	if (singleton == null) { // 第一次校验
		synchronized (Singleton.class) { // 对代码块加锁
			if (singleton == null) { // 第二次校验
				singleton = new Singleton();
			}
		}
	}
	return singleton;
}

方式三,静态内部类,利用内部类延时加载机制达到单例的延时创建效果,不需要锁,非常值得推荐和体会!完整代码:

public class Singleton {
	private Singleton() {
	}
	
    /**
     * 获取单例对象的静态方法
     *
     * @return 单例对象
     */
	public static Singleton getInstance() {
		return Holder.singleton;
	}
	
	// 静态内部类
	private static class Holder {
		private static final Singleton singleton = new Singleton();
	}
}

使用过程的线程安全性

不同业务场景,单例对象的业务逻辑是不一样的,操作的资源也不同,所以,就笼统的说一下处理思路:

  1. 确认单例对象操作的资源本身是否能够保证线程安全性,如果能,基本上就没有问题,不需要多做处理。
  2. 如果单例对象操作的资源本身不能保证线程安全性,就要使用各种锁机制,确保线程安全性。
  3. 如果操作资源涉及分布式系统,就要考虑分布式锁机制,这个时候有必要回头考虑一下,单例模式用的是否合适。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值