深入理解java单例模式
前言
相信在坐的码友们肯定在面试的过程中或多或少的被问到这个单例模式,反正对我而言,基本上每次的面试是必问的,所以在这里做一下总结.供有需要的朋友们看一下.在java中有23中设计模式,而单例模式也是设计模式的一种,在这里对23种设计模式就不做过多的讲解了,有兴趣的朋友可以看一下<java23种设计模式>.好了,话不多说,开干了!
一 单例模式的介绍
1.1 定义
保证一个类仅有一个实例,而且自行实例化并向整个系统提供这个实例。比如在线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象时候经常被设计成单例模式,只有一个实例,并且将其提供给系统。如果设计成多个就会出现很多不安全的问题。
1.2 分类
1.2.1 饿汉模式
public class Single {
//饿汉式单例类.在类初始化时,已经自行实例化
public static Single single = new Single();
private Single() {
}
public static Single getInstance() {
return single;
}
}
饿汉式就是在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的。
1.2.2 懒汉模式(线程不安全)
public class Single2 {
// 在被调用的时候没有直接实例化,用到的时候才开始实例化
public static Single2 single2;
private Single2() {
}
public static Single2 getInstance() {
single2 = new Single2();
return single2;
}
}
懒汉模式就是指全局的单例实例在第一次被使用时才构建,所以称其为懒汉。但是如果多个线程同时访问getInstance()方法时就会出现线程安全的问题,所以在多线程中不应该这样使用。
1.2.3 懒汉模式(加synchronized锁)
public class SynchronizedSingleton {
public static SynchronizedSingleton synchronizedSingleton;
private SynchronizedSingleton() {
}
public static synchronized SynchronizedSingleton getInstance() {
if (null == synchronizedSingleton) {
return new SynchronizedSingleton();
}
return synchronizedSingleton;
}
}
在实例化的时候加上synchronized锁可以解决线程安全问题,当有一个线程在实例化的时候,其余的就在外边等待,实例化之后,再进来的线程判断该实例不为空就直接返回该实例。 我们知道synchronized关键字偏重量级锁,所以在程序中每次使用getInstance() 都要经过synchronized加锁这一层,这难免会增加getInstance()的方法的时间消费,而且还可能会发生阻塞,所以效率比较低。
1.2.4 懒汉模式(双重检查锁)
public class DoubleLockSingleton {
public volatile static DoubleLockSingleton instance;
private DoubleLockSingleton() {
}
public DoubleLockSingleton getInstance() {
//检查实例,如果不存在,就进入同步代码块
if (null == instance) {
//只有第一次才彻底执行这里的代码
synchronized (DoubleLockSingleton.class) {
//进入同步代码块后,再检查一次,如果仍是null,才创建实例
if (null == instance) {
return new DoubleLockSingleton();
}
}
}
return instance;
}
}
我们可以看到该模式下是用了 volatile 关键字和 synchronized 锁,也称其为双重检查锁。volatile:(volatile 修饰的成员变量在每次被线程访问时,都强迫从主存(共享内存)中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到主存(共享内存)。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值,这样也就保证了同步数据的可见性。)因此volatile可以保证,当instance变量被初始化成DoubleLockSingleton实例时,多个线程可以正确处理instance变量。首先检查是否实例已经创建,如果尚未创建,才进行同步。这样以来,只有一次同步,可以大大减少getInstance() 的时间消费,所以提高了效率。
1.2.5 懒汉模式(静态内部类方式)
public class InnerClassSingleton {
// 私有内部类
private static class innerClass {
private static final InnerClassSingleton innerClassSingleton = new InnerClassSingleton();
}
private InnerClassSingleton() {
}
public static InnerClassSingleton getInstance() {
// 实例化的时候其实是调用的上边的内部类
return innerClass.innerClassSingleton;
}
}
静态内部类方式是将其实例变量私有,只有在创建实例的时候才调用其内部类,这样保证在加载该类的时候不会创建实例变量,只有在创建实例的时候才会调用其内部类,从而创建实例变量,这种方式既实现了线程安全,又避免了同步带来的性能影响。
1.3 懒汉模式和饿汉模式的总结
饿汉模式天生就是线程安全的,懒汉模式是不安全的,可以对其加锁使其变的线程安全,也可以使用双重检查锁即使其线程安全又提高其效率,或者使用内部类的方式即实现了线程安全,又不用给其加锁同步。当然了,至于你要用到哪一种单例模式,也要看情况而定的。如果你面试的时候也经常被问到单例模式,希望看完之后会对你有所帮助,你就可以 “咔咔咔” 一顿操作让面试管对你刮目相看。