最近项目中为了系统中避免创建多个实例,teamleader让我把项目代码优化一下(应用单例模式),后面将单例模式的学习心得分享给团队同事,同事也给了我一些意见,现在写出来分享给大家,博友多多知道哈,写的不好给点意见哈!
现在从三方面讲解单例模式:
(1)单例模式概念
(2)单例模式特点
(3)常见的实现方式
单例模式概念:作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。
单例模式特点:
1)单例类只能有一个实例
2)单例类必须自己创建自己的唯一实例
3)单例类必须给所有其他对象提供这一实例
常见的实现方式:从以下8中实现方式讲解
1)懒汉式,线程不安全
2)懒汉式,线程安全
3)恶汉式
4)恶汉式,变种
5)静态内部类
6)双重检验锁
7)登记式
1)懒汉式,线程不安全,代码如下:
public classSingleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance(){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
上述代码很简单,但是是线程不安全的,多线程情况下不能正常工作,会创建多个实例。为什么呢?
2)
懒汉式,线程安全,代码如下:
public classSingleton {
private staticSingletoninstance;
private Singleton(){
}
public staticsynchronized Singleton getInstance() {
if (instance== null) {#1
instance = newSingleton();#2
}
return instance;#3
}
}
当系统有两个线程线程1和线程2,两线程到达#1前,若线程1(或线程2)抢到锁,执行#2,#3后释放锁,若线程2(或线程1)抢到锁,会执行#2,由于instance为静态类变量,此时的instance已由线程1实例化了,因此线程2没有执行#2,因此系统只有一个实例化对象。所以该代码是线程安全的。缺点:尽管该代码是线程安全的,能够在多线程中很好的工作,而且看起来它也具备很好的lazy loading,但是效率低,99%情况下不需要加锁、解锁的同步操作。
3)恶汉式,代码如下:
public class Singleton {
private staticSingleton instance = new Singleton();
private Singleton (){
}
public staticSingleton getInstance() {
return instance;
}
}
上述代码在类一加载便实例化instance,因此称为恶汉式(很饥渴的意思,哈哈)
4)恶汉式,变种,代码如下:
public class Singleton {
private Singleton instance = null;
static {
instance = new Singleton();
}
private Singleton (){}
public static Singleton getInstance() {
return this.instance;
}
}
上述代码是3)的变种,其实是一样的。
5)静态内部类,代码如下:
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = newSingleton();
}
private Singleton (){}
public static final Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
相对于第三种方式,这种方式是Singleton类被装载了,instance不一定被初始化。因为SingletonHolder类没有被主动使用,只有显式通过调用getInstance方法时,才会显式装载SingletonHolder类,从而实例化instance。例如,如果实例化instance很消耗资源,我想让其延迟加载,另一方面,我不希望在Singleton类加载时就实例化,因为我不能确保Singleton类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化instance显然是不合适的。
6)
双重校验锁:
“双重检查加锁”的方式可以既实现线程安全,又能够使性能不受到很大的影响。
那么什么是”双重检查加锁“机制呢?
所谓双重检查加锁机制指的是:并不是每次进getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。
重检查加锁机制的实现会使用一个关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。(对于volatile修饰的变量,系统内的线程所有的write都将先行发生于read)
说明:由于volatile关键字可能会屏蔽掉虚拟机中的一些必要的代码优化,所以运行效率并不是很高。因此一般建议,没有特别的需要,不要使用。也就是说,虽然可以使用“双重检查加锁”机制来实现线程安全的单例,但并不建议大量采用,可以根据情况来选用。
现列出实例代码讲解:
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getInstance() {
if (singleton == null) {#2
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
注:很显然上述代码是线程安全的,然而细心的朋友可以发现,如果把去掉第一重判断#2,此时的代码也是线程安全的,与单例第二种实现方式没有本质的区别,那么为什么加上#2呢,好处有:提高代码运行效率,当线程1实例了singleton后,此时线程2判断singleton不为空,那么synchronized同步代码并需要执行,因为加锁、解锁很大的影响系统性能,尽量少用。
7)登记式,可继承:
细心的朋友可以看出懒汉式或者恶汉式的单例模式并不能被继承,并没有体现面向对象的优势,看到网上有博友实现的登记式代码很长很繁琐,因此贴出相对简洁的可继承的登记式单例模式的代码是很有必要的,我也是才学习到,是腾讯大神分享给我的。
下面代码是Google公司自己写的代码,不愧是牛逼公司,代码简洁,代码如下:
public abstract class Singleton {
private T mInstance;
protected abstract T create();
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}
由于静态类是需要继承才能实现起内部方法,是不是so easy!
如何继承呢?
public class AManager {
private static final Singleton sInstance = new Singleton() {
@Override
protected AManager create() {
return new AManager();
}
};
public static AManager getInstance(){
return sInstance.get();
}
private AManager(){
System.out.println("AManager created!");
}
}
上述代码很巧妙也很简单,就不用详细说明了哈,大家可以复制下来试试哈。
最后的最后,给出个总结:
单例模式在内存中只有一个对象,节省内存空间;避免频繁的创建销毁对象,可以提高性能;避免对共享资源的多重占用;可以全局访问。使用时不能用反射模式创建单例,否则会实例化一个新的对象,使用懒单例模式时注意线程安全问题,恶单例模式和懒单例模式构造方法都是私有的,因而是不能被继承的,有些单例模式可以被继承(登记式)。最后大家根据项目需要使用七种的一种实现方式哈。