Java中单例模式定义:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”
单例模式的实现主要依托static关键字。
单例模式的两种类型1.饿汉模式:实例的创建出现在类加载阶段;
2.懒汉模式:实例的创建在第一次使用对象时。
static class Singleton{
//期望singleton是一个单例类,只有一个实例
//先创建一个成员,保存唯一的一个Singleton实例
private static Singleton instance = new Singleton();
//再提供一个方法,来获取到这个实例
public static Singleton getInstance(){
return instance;
}
//然后把类的构造方法设为private,防止其他代码再创建实例
private Singleton(){
}
}
public static void main(String[] args) {
//只能通过getInstance方法来获取实例,
//无法通过new的方法创建新的Singleton实例
Singleton s = Singleton.getInstance();
}
懒汉模式代码
//创建实例的时候是第一次使用getInstance方法的时候,比饿汉模式要迟
static class Singleton{
private static Singleton instance = null;
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
private Singleton(){
}
}
public static void main(String[] args) {
Singleton s = Singleton.getInstance();
}
线程安全问题
饿汉模式在类加载时已经创建好该单例对象,在获取单例对象时直接返回对象即可,不存在线程安全问题。
懒汉模式在使用对象时才实例化对象,所以存在线程安全问题。
如何保证懒汉模式的线程安全?
1.加锁
如果把synchronized加到getInstance方法外面,则相当于针对“判断、new、返回”三个操作进行加锁,这三个操作都是串行的,效率会更低;
如果把synchronized加到getInstance方法内部,则相当于针对“判断、new”两个操作进行加锁,效率相对会高一点,同时也保证了线程安全;如下图所示。
可以保证线程2的LOAD一定在线程1的SAVE后面;
代码如下:
public static Singleton getInstance(){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
return instance;
}
此时线程的安全可以保证了,但效率会大大降低,因为每次调用getInstance方法都会涉及到加锁解锁,但实际上我们只有在instance未初始化时才涉及到线程安全问题,后面instance初始化之后只需要进行读操作即可,不会涉及到线程安全问题;所以只需要首次调用getInstance方法时加锁即可,后续就不用再调用了。所以需要引入双重if判定。
2.双重if判定
if(instance == null){
synchronized(Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
引入双重if判定就可保证加锁操作不再重复添加;
3.volatile
但是由于instance涉及到了读写操作,所以可能出现内存可见性的问题,即instance修改之后,线程2本应该从内存中读取不为空,但是可能是从寄存器中读取未被修改的instance,导致读出的数据为null,所以需要再添加一个volatile,保证外层if读取到的值是内存中的最新值。
volatile private static Singleton instance = null;总结
线程安全的单例模式,涉及的要点主要是3点:
1.加锁(在合适的位置加锁,保证把if和new都包裹起来,同时不要范围太大);
2.双重if(保证需要加锁的时候才加锁,一旦初始化完毕,就不必再加锁了);
3.volatile保证外层if读操作,读到的值都是内存中的最新的值。



