前言
我们都知道,单例模式是设计模式里最简单的模式,无论是代码还是模式的理解都是最简单的,但是那么简单的东西,你真的写对了吗?
单例模式
单例模式——确保一个类只有一个实例,并提供全局访问点。
要点:
- 确保程序中一个类最多只有一个实例。
- 提供访问这个实例的全局点。
乍一看,确实简单,也很好理解,看看怎么实现的,代码:
/**
* 单例模式
* @author herongqin
*/
public class RedisSingleton {
private static RedisSingleton redisSingleton;
private RedisSingleton(){
}
public static RedisSingleton getInstance(){
if (redisSingleton == null){
redisSingleton = new RedisSingleton();
}
return redisSingleton;
}
}
说明:目的为了演示,大家不需要关心Redis的内容。
简单吧,把构造器 private
不让别人进行实例化,然后提供一个对外实例化的静态方法,如果想使用这个实例,那就必须通过 getInstance()
方法进行获取具体的实例,是不是满足了单例模式的2个要点?确实是,但是,有句“古话”说得好:程序员要把任何一个应用都当成多线程应用。
提问:如果有多个线程同时去访问getInstance()
,拿到的能确保是同一个实例吗?
public static RedisSingleton getInstance(){
if (redisSingleton == null){
redisSingleton = new RedisSingleton();
}
return redisSingleton;
}
那咋搞?这不是违背了单例的核心原则(最多只有一个实例
)了吗?有经验的同学,可能已经意识到了,给这段代码加锁啊[赞]。
给 getInstance 加同步锁
我们在 getInstance()
方法上加 synchronized
关键字实现同步锁,这个时候,每个人进入这个方法前,都需要等待上一个线程结束之后,才能进入这个方法,这个时候就可以保证最多只有一个实例了。代码:
public static synchronized RedisSingleton getInstance(){
if (redisSingleton == null){
redisSingleton = new RedisSingleton();
}
return redisSingleton;
}
打完收工~!
后来,项目越来越牛B了,自己写的这个单例越来越多的地方在使用,然后他们就发现自己写的代码怎么越来越慢,经过排查,就是因为自己写的这个单例引起的,因为不管你多少人,你都必须先等待上一个人拿完了他才能继续拿,已经验证影响别人的使用了,咋搞?当然是优化了(谁叫甲方是Babababa,哈哈,开玩笑)。
使用 volatile 做双重检查
在静态变量上用volatile
关键字进行修饰,保证变量可见性(多线程下),禁止jvm对该变量进行指令重排,保证了有序性。
/**
* 单例模式
* @author herongqin
*/
public class RedisSingleton {
private static volatile RedisSingleton redisSingleton;
private RedisSingleton(){
}
public static synchronized RedisSingleton getInstance(){
// 第1次检测
if (redisSingleton == null){
synchronized (RedisSingleton.class){
// 第2次检查
if (redisSingleton == null){
redisSingleton = new RedisSingleton();
}
}
}
return redisSingleton;
}
}
在上面的代码中,synchronized
只会锁其中一个片段,而且因为volatile
只会执行一次,所以确保了最多一个实例的特性。
嗯~~ 是不是有点复杂,有没有简单点的,有的!有个更简单的,因为它天生的线程安全,以及默认的private 的构造器,那就是使用枚举实现单例模式。
使用枚举实现单例模式(推荐)
/**
* 单例模式
* @author herongqin
*/
public enum RedisSingleton {
INSTANCE;
public void set(String key, Object value){
// 其他代码
}
public String getString(String key){
// 其他代码
return "";
}
public static void main(String[] args) {
// test
System.out.println(RedisSingleton.INSTANCE.getString("key"));
}
}
简单吧,但是得从我们正常使用枚举的思维跳出来。
总结
程序员应该把任何一个程序都当成是多线程。共勉~