【剑指offer】面试题2:实现 Singleton 模式

本文目录:

懒汉式和饿汉式

解法1:只适用于单线程环境 (不好)

解法2:虽然在多线程环境中能工作,但是效率不高 (不好)

解法3:加同步锁前后两次判断实例是否存在  (可行)

解法4:利用类静态变量初始化一个实例  (推荐使用)

解法5:静态内部类实现按需创建实例 (推荐使用)

总结


题目:设计一个类,我们只能生成该类的一个实例。

考点:对单例模式(Singleton)模型的理解。

 只能生成一个实例的类是实现了Singleton(单例)模式的类型。

开篇我们先说明一个问题:“懒汉式和饿汉式”的是什么意思?

懒汉式和饿汉式

所谓“懒汉式”与“饿汉式”的区别就是:在建立单例对象的时间的不同。

懒汉式:在你真正用到的时候才去创建这个单例对象

public class Singleton(){
    
    private static Singleton singleton = null;   // 只是声明一个singleton对象,并没有创建

    private Singleton(){
    
    }

    public static synchronized Singleton getInstance(){
        if(singleton == null){            // 先判断是否为空
            singleton = new Singleton();  // 懒汉式做法
        }
        
        return singleton;
    }
}

饿汉式:不管你会不会用上,一开始就创建这个单例对象

public class Singleton(){
    
    private static Singleton singleton = new Singleton();   // 只是声明一个singleton对象,并没有创建

    private Singleton(){
    
    }

    public static synchronized Singleton getInstance(){
        
        return singleton;   // 直接返回创建好的singleton对象
    }
}

解法1:只适用于单线程环境 (不好)

由于要求只能生成一个实例,因此我们必须把构造函数设为私有函数已禁止他人创建实例。我们可以定义一个静态实例,在需要的时候创建该实例。代码如下所示:

public class Singleton1 {

	private static Singleton1 instance = null;
	
        // 私有的构造函数
	private Singleton1(){
		
	}
	
	public static Singleton1 getInstance(){
		if(instance == null){
			instance = new Singleton1();
		}
		return instance;
	}
}

Singleton的静态属性instance,只有instance为null的时候才创建一个实例,构造函数私有,确保每次都只创建一个,避免重复创建。

缺点:只在单线程的情况下正常运行,在多线程的情况下,就会出问题。例如:当两个线程同时运行到判断instance是否为空的 if 语句,并且instance确实没有创建好时,那么两个线程都会创建一个实例,此时类型Singleton1就不满足单例模式的要求了。


解法2:虽然在多线程环境中能工作,但是效率不高 (不好)

在解法1的基础上加上了同步锁,使得在多线程的情况下可以用。

public class Singleton2 {
	
	private static Singleton2 instance = null;
	
	// 私有的构造函数
	private Singleton2(){
		
	}
	
	// 同步方法
	public static synchronized Singleton2 getInstance(){
		if(instance == null){
			instance = new Singleton2();
		}
		return instance;
	}
}

当两个线程同时想创建一个实例,由于在同一个时刻只有一个线程能得到同步锁,当第一个线程加上锁以后,第二个线程只能等待。当第一个线程发现实例还没有创建,则创建。接着第一个线程释放同步锁,第二个线程才可以加上同步锁,执行下面的代码。由于第一个线程已经创建了实例,所以第二个线程不需要创建实例。保证在多线程的环境下也只有一个实例。

缺点:每次通过 getInstance 方法得到 singleton 实例的时候都有一个试图去获取同步锁的过程。而众所周知,加锁是很耗时的,这也是这种方法效率低下的主要原因,在没有必要的时候要尽量避免。


解法3:加同步锁前后两次判断实例是否存在  (可行)

我们只是在还没有创建之前需要加锁操作,以保证一个线程创建出实例。而当实例已经创建之后,我们就不需要再执行加锁操作了。

public class Singleton3 {

	private static Singleton3 instance = null;
	
	// 私有的构造函数
	private Singleton3(){
		
	}
	
	// 两次 if 判断
	public static Singleton3 getInstance(){
		
		if(instance == null){
			// 若instance为null,说明它还未创建,则进行加锁操作
			synchronized (Singleton3.class) {
				if(instance == null){
					instance = new Singleton3();
				}
			}
		}
		return instance;
	}
}

Singleton3 中只有当 instance 为 null 即没有创建时,需要进行加锁操作。当 instance 已经创建出来之后,则无需再进行加锁。因为只在第一次的时候 instance 为 null,因此只在第一次试图创建实例的时候需要加锁。这样,Singleton3 的时间效率比 Singleton2 要好的多。

Singleton3 用加锁机制来确保在多线程环境下创建一个实例,并且用两个 if 判断来提高效率。这样的代码实现起来比较复杂,容易出错,下面看两种比较优秀的解法。


解法4:利用类静态变量初始化一个实例  (推荐使用)

其实就是饿汉式,在初始化静态变量 instance 的时候创建一个实例。

public class Singleton4(){
    
    private static Singleton4 singleton = new Singleton4();   // 只是声明一个singleton对象,并没有创建

    private Singleton4(){
    
    }

    public static synchronized Singleton4 getInstance(){
        
        return singleton;   // 直接返回创建好的singleton对象
    }
}

初试化静态的 instance 创建一次。如果我们在Singleton4类里面写一个静态的方法不需要创建实例,它仍然会早早的创建一次实例。而降低内存的使用率。

缺点:没有懒加载的效果,从而降低内存的使用率。


解法5:静态内部类实现按需创建实例 (推荐使用)

public class Singleton5 {

	// 私有的构造函数
	private Singleton5(){
		
	}
	
	// 私有的内部类
	private static class SingletonHolder{
		private final static Singleton5 instance = new Singleton5();
	}
	
	public static Singleton5 getInstance(){
		return SingletonHolder.instance;  // 返回SingletonHolder中创建的实例
	}
}

注解:定义一个私有的内部类,在第一次用这个内部类时,会创建一个Singleton5实例。而类型为SingletonHolder的类,只有在Singleton5.getInstance()中调用,由于私有的属性,他人无法使用SingletonHolder,所以即便调用Singleton.getInstance()也不会创建实例。

优点:达到了懒加载的效果,即按需创建实例。


总结:

第1种方法:多线程环境下不能工作;

第2种方法:虽然能够在多线程环境下工作,但是加锁耗时,效率不高;

第3种方法:通过两次 if 判断实现只有 instance为 null 时才加锁,实现多线程下高效的工作,但是代码逻辑相对复杂点;

第4种方法:通过直接创建出一个实例给类的静态变量,即”饿汉式“;

第5种方法:通过私有的内部类方法完成按需创建。

面试中推荐后第4种或者第5种解法。

猜你喜欢

转载自blog.csdn.net/pcwl1206/article/details/85374961