设计模式–单例模式(Singleton)
确保一个类只有一个实例,并提供该实例的全局访问点。
1、懒汉式–线程不安全
私有静态变量 instance 被延迟加载,只在第一次用到时进行实例化,若没有用到该类,就不会进行实例化,从而节约资源。
该方式在多线程环境下是线程不安全的,如果多个线程同时进入if (instance == null)
,并且此时
instance
为 null, 将会使 instance
实例化多次
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
2、饿汉式–线程安全
不管该类有没有用到,采取直接实例化的方式,这样也不会有线程安全问题。但丢失了延迟实例化带来的节约资源的好处
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
}
3、懒汉式–线程安全
只要对 getInstance()
方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免多次实例化
但是当一个线程进入该方法后,其他试图进入该方法的线程不管 instance
有没有被实例化,都必须等待。将使线程阻塞时间过长,因此存在性能问题。
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance(){
if (instance == null){
instance = new Singleton();
}
return instance;
}
}
4、双重校验锁–线程安全
instance
只要被实例化一次,之后就可以直接使用了。因此加锁操作只需对实例化代码部分进行。双重校验先判断 instance
是否已经实例化, 没有实例化在对实例化操作加锁。
public class Singleton {
private volatile static Singleton instance;
private Singleton(){}
public static Singleton getInstance(){
if (instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
为何要用两个 if 语句进行判断?
考虑当只有一个 if 的时候,在 instance == null
的情况下,当多个线程都来执行时,虽然有加锁操作,但最终多个线程都会去执行加锁代码块中的内容,又将导致 instance
实例化多次。因此必须使用双重 if 判断。
为何要使用 volatile 关键字修饰 instance?
对象实例化时分为三步执行:
1、为 instance
分配内存空间
2、初始化 instance
3、将 instance
指向分配的内存地址
由于JVM具有指令重排的特性,以上三步执行顺序可能发生改变。当线程1执行了 1、3步骤之后,线程2执行了方法 getInstance()
发现 instance
不为空,直接返回了 instance
。但此时 instance
还未被初始化。使用 volatile
可以禁止JVM的指令重排,保证多线程下也能正常执行。