单例模式
一、单例模式(Singleton Pattern)的定义
1、单例模式 就是被单例的对象只能有一个实例存在。
2、单例模式的实现方法是:一个类能返回对象的一个引用(永远是同一个)和一个获得该唯一实例的方法(必须是静态方法)。
3、通过单例模式,我们可以保证系统中只有一个实例,从而在某些特定的场合下达到节约或者控制系统资源的目的。
二、单例模式类图
在【装饰者模式】中,一个人拥有各种不同特征的女朋友,但最后只能拥有一个老婆。
三、示例代码
1、饿汉模式
饿汉模式就是很饥渴,所以一上来就需要给它创建一个实例。
缺点:不管有没有调用过获得实例的方法(getWife()),每次都会新建一个实例。
// 饿汉模式
public class Wife {
// 一开始就新建一个实例
private static final Wife wife = new Wife();
// 默认构造方法
private Wife() {}
// 获得实例的方法
public static Wife getWife() {
return wife;
}
}
2、懒汉模式
懒汉模式:一开始不新建实例,只有当它需要使用的时候,会先判断实例是否为空,如果为空才会新建一个实例来使用。
// 懒汉模式
public class Wife {
//一开始没有新建实例
private static Wife wife;
private Wife() { }
// 需要时再新建
public static Wife getWife() {
if (wife == null) {
wife = new Wife();
}
return wife;
}
}
3、线程安全的懒汉模式
懒汉模式的缺点:如果有多个线程并行调用getWife()时,还是会创建多个实例,单例模式就失效了。
——改进:设为线程同步(synchronized)
——synchronized的作用:保证在统一时刻最多只有一个线程运行,避免多线程带来的问题。
// 懒汉模式(线程安全)
public class Wife {
private static Wife wife;
private Wife() { }
// 添加了 synchronized 关键字
public static synchronized Wife getWife() {
if (wife == null) {
wife = new Wife();
}
return wife;
}
}
4、双重检验锁double check
线程安全的懒汉模式:解决了多线程问题,但是效率不高(每次调用获得实例方法getWife()时要进行同步,但多数情况下并不需要同步操作。)
——改进:当wife实例不为空,则可以直接使用,就不需要给getWife()加同步方法,直接返回wife实例就可以了。
// 双重锁的 getWife() 方法
public static Wife getWife() {
// 第一个检验锁,如果不为空直接返回实例对象,为空才进入下一步
if (wife == null) {
synchronized (Wife.class)
{ //第二个检验锁,因为可能有多个线程进入到 if 语句内
if (wife == null) { wife = new Wife(); } }
}
return wife ;
}
——存在的问题:因为在JVM指向这句代码的时候,要做好几件事情,而JVM为了简化代码,有可能造成这几件事情的执行顺序是不固定的,从而造成错误。
——改进:给实例加一个volatile关键字(作用:防止编译器自行优化代码)
// 双重检验锁
public class Wife {
private
volatile static Wife wife;
private Wife() { }
public static Wife getWife() {
if (wife == null) {
synchronized(Wife.class) {
if (wife == null) {
wife = new Wife();
}
}
}
return wife;
}
}
5、静态内部类
静态内部类利用JVM自身的机制来保证线程安全,因为WifeHolder类是私有的,除了getWife()之外没有其他方式可以访问实例对象,而且只有在调用getWife()时才会去真正创建实例对象。
// 静态内部类
public class Wife {
private static class WifeHolder {
private static final Wife wife = new Wife();
}
private Wife() { }
public static Wife getWife() {
return WifeHolder.wife;
}
}
6、枚举
// 枚举
public enum Wife {
INSTANCE;
// 自定义的其他任意方法
public void whateverMethod() { }
}
可以通过Wife.INSTANCE来访问实例对象,比getWife()简单,而且创建枚举默认就是线程安全的,还可以防止反序列化带来的问题。
参考: