单例模式——独一无二的对象

单例模式

1 概述

1)简介
单例对象的类只允许一个实例存在,并提供一个全局访问点。

2)使用场景
有些对象只需要一个的时候,比如线程池、缓存、日志对象等。如果制造出多个实例,就会导致许多问题的产生。

3)为什么不使用全局变量 ?
虽然Java的静态对象可以做到,但如果将对象赋值给一个全局变量,那么我们必须在程序一开始就创建好对象。而恰巧这个对象非常消耗资源,程序在这次运行过程中又一直没有用到它,就会形成浪费。如果我们利用单例模式,我们就可以在需要时才创建对象(延迟实例化)。

2 实现

2.1 懒汉式

懒汉式单例模式可以保证延迟初始化,但并不保证线程安全,类图如下所示。

public class Singleton {
	private static Singleton uniqueInstance;   //利用静态对象记录类的唯一实例
	
	private Singleton() {}   //私有构造器
	
	//利用getInstance()方法实例化对象,并返回实例
	public static Singleton getInstance() {
		if(uniqueInstance == null) {
			uniqueInstance= new Singleton();
		}
		return uniqueInstance;

	//其他方法……
}
  • 私有的构造器可以确保无法从外部创建实例,只能够通过getInstance()方法创建实例。如果我们不需要这个实例,它就永远不会产生,这就是上文中所说的“延迟实例化”。
  • 这只是最基本的实现,它不支持多线程,因为没有加锁 synchronized。当有多个线程同时调用getInstance()方法时,就有可能创建出多个实例。

2.2 错误的双重检查加锁

曾经这是一种被认为“聪明”的技巧,但其实是一种完全错误的优化。

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

	public static getInstance() {
		//只有第一次实例化时,才会彻底执行以下的代码
		if(uniqueInstance == null) {
			synchronized (Singleton.class) {
				//加入区块后,再检查一次
				if(uniqueInstance == null) {
					uniqueInstance = new Singleton();
				}
			}
		}
		return uniqueInstance;
	}
}

为什么错误呢?
因为在类的初始化时,其实发生了如下的步骤:

  1. 分配对象的内存空间
  2. 初始化对象
  3. 设置对象实例指向刚分配的内存地址

问题就在于2、3两个步骤在编译的时候可能会被重排序,即代码执行顺序变为了(1,3,2)。导致在多线程情况下A线程正在执行步骤2初始化对象时,而B线程却可能看到一个未被初始化的对象(内存可见性),所以会判断uniqueInstance 不为null,进而再去创建一次对象,导致Singleton被创建了两次。

2.3 volatile双重检查加锁

方式一时加上volatile关键字,以禁止创建对象的2、3步骤被重排序。这种方案需要JDK5及以上版本。

public class Singleton {
	private volatile static Singleton uniqueInstance;
	
	private Singleton() {}

	public static getInstance() {
		//只有第一次实例化时,才会彻底执行以下的代码
		if(uniqueInstance == null) {
			synchronized (Singleton.class) {
				//加入区块后,再检查一次
				if(uniqueInstance == null) {
					uniqueInstance = new Singleton();
				}
			}
		}
		return uniqueInstance;
	}
}

2.4 内部类方式

使用内部类这种方式同样能够保证在延迟初始化与多线程安全。线程安全的原因是,每一个类或接口,都有一个唯一的初始化锁,当类初始化时会进行加锁,以保证只有一个线程进入,初始化完成后释放锁。

public class Singleton {    
    private Singleton() {}
       
    private static class SingletonHolder {
        private static Singleton instance = new Singleton();
    }
    
    public static Singleton getInstance() {
    	// 导致SingletonHolder类被初始化
        return SingletonHolder.instance;
    }
}

3 总结

文中共提到了三种实现单例模式的方式(一种线程不安全的懒汉式实现,一种线程安全的volatile双锁式实现,一种线程安全的内部类实现)。关于单例模式还有更多种的实现方式,当考虑使用何种方式时,这需要先确定在性能和资源上的限制,然后再选择适合自己的程序的方式去实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值