提起单例模式,我们一般首先想到的就是Spring 框架中的Bean的作用域singleton,当一个bean的作用域为单例的时候,这个bean在Spring 的容器中只有一个实例化的对象。那么我们如何实现一个单例对象呢?
首先,构造函数私有化,这样子就只能在该类中调用new 方法创建该类的对象。其次,变量必须是类变量来接收这个创建的实例对象;最后提供一个公共的静态方法,对外提供获取该唯一实例的入口,并且在该方法中判断是否实例化过,否则进行实例化。
基于以上思考我们写下案例:
public class SingletonObj {
private static SingletonObj instance;
private SingletonObj() {
}
public static SingletonObj getInstance() {
if (instance == null) {
instance = new SingletonObj();
}
return instance;
}
}
这种写法是所谓的非线程安全的懒汉懒加载,因为getInstance()没有做线程安全的处理,所以非线程安全;并非在类加载的时候创建SingletonObj实例,所以是懒加载;懒汉是因为直到使用的时候才实例化。那么我们在进行修改,使它变的线程安全:
public class SingletonObj {
private static SingletonObj instance;
private SingletonObj() {
}
public static synchronized SingletonObj getInstance() {
if (instance == null) {
instance = new SingletonObj();
}
return instance;
}
}
现在它变得线程安全了,因为我们在getInstance()方法上添加了 synchronized关键字(synchronized实现线程安全,请看博客https://blog.youkuaiyun.com/tony_java_2017/article/details/81362591)。但是这样子getInstance()方法的并发性就降低了,因此,我们可以减小锁粒度,将锁的范围修改为只在创建的时候加锁:
public class SingletonObj {
private volatile static SingletonObj instance ;
private SingletonObj() {
}
public static SingletonObj getInstance() {
if (instance == null) {
synchronized (SingletonObj.class) {
if (instance == null) {
instance = new SingletonObj();
}
}
}
return instance;
}
}
修改后,只要instance实例化过了,那么就会直接返回,如果没有实例化,则使用线程安全的方式进行实例化。volatile 关键字保证了内存可见性。
假如该单例对象的创建过程比较耗时,因此我想获取实例的时候就已经初始化完成了。这样子系统的响应即使在第一次使用该单例也会比较快,那么我们做下适当的调整,让instance的初始化放在类的初始化时:
public class SingletonObj {
private static SingletonObj instance = new SingletonObj();
private SingletonObj() {
}
public static SingletonObj getInstance() {
return instance;
}
}
修改后,因为类变量instance的创建是在类加载的时候创建的,那么它也是线程安全的,既然instance是线程安全的 ,我们在getInstance()中就不需要synchronized 关键字了。这就是一种饿汉单例的线程安全实现,既然类加载机制可以实现instance的线程安全,那么我们可以基于此机制实现一个线程安全并且并发高的懒加载的线程安全实现:
public class SingletonObj {
private static class Inner{
private static final SingletonObj instance = new SingletonObj();
}
private SingletonObj() {
}
public static SingletonObj getInstance() {
return SingletonObj.Inner.instance;
}
}
以上实现还存在一个缺点就是当是一个反射的时候,依然可以创建 SingletonObj的实例,因此,可以说我们还是没有做到全局唯一实例的要求,例如我使用下面的方式也是可以创建一个实例的:
public class SingletonDemo {
public static void main(String[] args) throws Exception {
Class<SingletonObj> class1 = (Class<SingletonObj>) Class.forName("com.thread.test.SingletonObj");
Constructor<SingletonObj> constructor = class1.getDeclaredConstructor(null);
//私有的构造函数必须设置true,否则无法调用
constructor.setAccessible(true);
System.out.println(constructor.newInstance(null));
}
}
那么我们如何实现不能使用反射进行实例创建呢?只要在构造函数中加上一个为空的判断,不为空则抛异常:
private SingletonObj() {
if(instance != null) {
throw new UnsupportedOperationException("该对象为单例不能重复实例化");
}
}
则一旦已经实例化过之后,再通过反射调用就会抛异常。还有一种单例的实现方式在Effective Java中被作者Joshua Bloch提倡,那就是枚举类。它既能支持序列化,同时避免了多线程的同步问题还防止反序列重新创建新的对象,而且代码上更加简洁。
书中的例子如下:
public enum Elvis {
INSTANCE;
private String[] favoriteSongs = {"Hound Dog","heartbreak Hotel"};
public void printFavorites() {
System.out.println(Arrays.toString(favoriteSongs));
}
}
然后我们再次进行反射创建实例会发现报错了。java.lang.NoSuchMethodException: com.thread.test.Elvis.<init>()