单例模式[读书笔记]

对于系统中的某些类来说,只有一个实例很重要,例如,一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。当这个唯一实例创建成功之后,我们无法再创建一个同类型的其他对象,所有的操作都只能基于这个唯一实例。为了确保对象的唯一性且易于访问,我们可以通过单例模式来实现,这就是单例模式的动机所在。


单例模式

单例模式(Singleton Pattern):必须确保类只有一个实例,而且自行实例化向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。

单例模式的要点有三个:
1.确保某一个类只能有一个实例,禁止外部直接使用new来创建对象
2.必须是类自行创建这个实例
3.必须自行向整个系统提供这个实例

伪代码

//按钮类
public class Button{
	//私有构造函数,设置为private禁止外部直接new创建对象
	private Button(){...} 
	//私有静态成员变量,存储唯一实例
	private static Button btn = null;
	//公有静态成员方法,返回唯一实例
	public static getButton(){
		if(btn == null){
			btn = new Button();
		}
		return btn;
	}
}
//客户端
public class Client{
	public static void main(String[] args){
		Button myBtn = Button.getButton();
	}
}

饿汉式、懒汉式、双重检查锁定

在多线程情况下,上述代码将不再保证只产生唯一实例,或者说是线程不安全的。例如,有两个线程A,B同时访问getButton()方法,线程A先抢到CPU时间片执行,该线程在执行到初始化对象但还未完成时发生了CPU时间片的切换。由于实例未创建完成,线程B执行时通过了判断语句并创建了实例对象,等线程B结束后,线程A拿到时间片继续执行上次执行未完成的任务,又创建了一个实例对象,导致最终创建了多个实例,违背了单例模式。

1.饿汉式单例类

class EagerSingleton{
	//类加载时instance完成实例化,确保了单例
	private static final EagerSingleton instance = new EagerSingleton();
	//私有构造函数
	private EagerSingleton(){...}
	//返回实例
	public static EagerSingleton getInstance(){
		return instance;
	}
}

instance 在类装载时就实例化,确保了只有一份。但是没有懒加载,可能会浪费内存。


final

1)如果某个成员变量用final修饰,JVM规范做出如下明确的保证:一旦该变量的引用对象对其他线程可见,则其final成员也必须已经完成初始化了。

2)final关键字禁止cpu指令集重新排序,来保证对象的安全发布,防止对象引用被其他线程在对象被完全构造完成前拿到并使用。

3)final修饰的成员变量还有另一个特性,如果是基本类型,则值不能改变。如果是引用类型,则地址不能改变。

final关键字保证了多线程下变量的数据安全性。


2.懒汉式单例类

class LazySingleton{
	private static LazySingleton instance = null;
	private LazySingleton(){...};
	//加同步锁
	public synchronized static LazySingleton getInstance(){
		if(instance == null){
			instance = new LazySingleton();
		}
		return instance;
	}
}

懒汉式单例类在getInstance()前加了synchronized关键字进行同步锁,以处理多线程同时访问的问题。虽然解决了问题,但是每次调用getInstance()方法时都需要进行线程锁定判断进而线程阻塞,在多线程高并发访问环境中,会导致系统性能大大下降。如何既解决了线程安全问题又可以不影响系统性能呢?

我们无须对整个getInstance()方法进行锁定,只需对其中的代码“instance = new LazySingleton();”进行锁定即可。

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

但是这样又存在线程安全问题了,需进行进一步改造。

3.双重检查锁定(Double-Checking Locking)

class LazySingleton{
	private static volatile LazySingleton instance = null;
	private LazySingleton(){...};
	//双重检查
	public static LazySingleton getInstance(){
		if(instance == null){
			synchronized(LazySingleton.class){
				if(instance == null){
					instance = new LazySingleton();
				}
			}
		}
		return instance;
	}
}

双重检查锁单例写法解决了线程安全问题和性能问题,使用双重锁定检查实现懒汉式单例,需要在静态变量前加修饰符volatile,保证变量在多线程之间的可见性以及指令的有序性。

静态内部类单例

双重检查锁单例写法虽然解决了线程安全和性能问题,但终归要使用synchoronized,对性能还是存在一定影响。

饿汉式单例不能实现延迟加载,不管将来用不用终究要占用内存,可能会带来内存浪费问题。

静态内部类

public class InnerClassSingleton{
	//私有构造方法
	private InnerClassSingleton(){}
	//静态内部类
	private static class LazyHolder{
		private static final InnerClassSingleton instance = new Singleton();
	}
	//如果调用getInstance方法,则不会加载静态内部类
	public static InnerClassSingleton getInstance(){
		return LazyHolder.instance;
	}
}

利用Java语法的特点,默认不加载内部类。只有调用getInstance方法时才会加载内部类完成类的初始化,同时JVM确保了static和final修饰的变量只有一份。


枚举单例

静态内部类无法防止利用反射来重复构建对象【后续学习…】

public enum EnumSingleton{
	INSTANCE;
	public static EnumSingleton getInstance(){
		return INSTANCE;
	}
}

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值