【设计模式】单例模式

单例模式

设计模式的实现大多建立在Java提供的一些关键字以及面向对象的设计上,通过继承/组合,形成了几种经典的范式。

单例模式是23种设计模式中最为简单的一种,如果拿来暑期集训讲static关键字的时候结合单例模式一起讲,可能效果会不错~

单例模式用于这样一种场景,一个全局配置的类,或者说一种“管理者”,来方便资源的共享,管理,减少性能损耗,比如Windows的应用管理器,比如MySQL的数据库连接池,线程池。

定义

单例模式确保一个类只有一个实例,并提供一个全局访问点。

全局访问点:当需要实例时,向类查询,它会返回单个实例。

实现

非线程安全

不考虑线程安全的情况,最基本的单例模式实现,就可以用到static关键字,让实例化的类变成其静态属性,在实例化前先去访问这个static修饰的变量有无赋值,未赋值的情况下才去创建实例,以达到只创建一个对象的效果。

代码:

// NOTE: This is not thread safe!
// 这是用的时候再去创建这个唯一实例
// “延迟实例化”

public class Singleton {
	private static Singleton uniqueInstance;
	//利用一个修饰符static来确保这是个静态变量,进而保证其唯一性
 
	private Singleton() {}
 
	public static Singleton getInstance() {
		if (uniqueInstance == null) {
			uniqueInstance = new Singleton();
		}
		return uniqueInstance;
	}
 
	// other useful methods here
	public String getDescription() {
		return "I'm a classic Singleton!";
	}
}

线程安全

增加synchronized关键字

但是在多个线程都在实例化这个类的情况下,假设说在最开始,一个线程先执行到if (uniqueInstance == null) {,另一个线程也紧接着执行到if (uniqueInstance == null) {,两个if判断都会为true,那么会有两个线程实例化该类。

实现

如何阻止两个线程同时执行到if判断这里呢?最简单,最直接的思考就是增加synchronized关键字去保护这段代码。

public class Singleton {
	private static Singleton uniqueInstance;
 
	private Singleton() {}
 
	public static synchronized Singleton getInstance() {
        //在这里使用sychronized关键字
		if (uniqueInstance == null) {
			uniqueInstance = new Singleton();
		}
		return uniqueInstance;
	}
 
	// other useful methods here
	public String getDescription() {
		return "I'm a thread safe Singleton!";
	}
}

这里,getInstance(),是一个静态方法,因为synchronized关键字不是对类上锁,就是对实例化的对象上锁,显然这就是对Singleton类上锁了,两个线程同时想去实例化Singleton类,但只有一个能顺利执行下去。

不足

但是每次去判断是否已经被实例化,都要去走synchronized修饰的代码段,会带来一定的性能损耗,因此我们继而可以思考其他的实现方案。

“直接创建实例”而非延后创建

在静态初始化器中直接创建对应的实例,避免后续的判断,甚至这种思路是思考如何写单例模式的代码中最为直接的实现方案。

public class Singleton {
	private static Singleton uniqueInstance = new Singleton();
 
	private Singleton() {}
 
	public static Singleton getInstance() {
		return uniqueInstance;
	}
	
	public String getDescription() {
		return "I'm a statically initialized Singleton!";
	}
}

“双重检查加锁”,减少同步次数

想去检查实例是否已创建,如果程序判断出“尚未创建”,才会进行同步,这样一来“同步”的代码只会在第一次会被执行,因此优化了性能。

这种是将我们的任务进行更为细粒度的拆分,实现代码如下:

public class Singleton {
	private volatile static Singleton uniqueInstance;
	//注意这里使用了volatile关键字
 
	private Singleton() {}
 
	public static Singleton getInstance() {
		if (uniqueInstance == null) {
			//检查实例,如果不存在,就进来
			synchronized (Singleton.class) {
				if (uniqueInstance == null) {
					//再次检查
					uniqueInstance = new Singleton();
				}
			}
		}
		return uniqueInstance;
	}
}

  1. 这里使用了volatile关键字,是为了保证不同线程对这个变量进行操作时的可见性(即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的)。具体可以深入去看Java内存模型的实现。
  2. 同步代码块中,锁为syncronized括号中配置的对象。这里对Singleton.class进行加锁,是让synchronized保护的是类本身。

其他

类加载器

每个类加载器都定义了一个命名空间,如果有两个类以上加载器,就会出现“多个单件并存”的怪异现象,这时需要指定同一个类加载器。

继承单件类

可以继承单件类,但会让所有的子类都共享一个实例变量。

总结

单例模式虽然很简单,描述的也是单个类的事情而非其他设计模式中强调多个类之间的联系,但是需要结合多线程,JVM的知识去看,才能真正掌握“一个类只有一个实例”的实现方案。

“急切实例化”与“延迟实例化”也是单例模式中重点需要去关注的东西,如果想“延迟”去做某些事情,就要付出额外的思考~

原文地址:https://www.cnblogs.com/buzhouke/p/14773717.html