面试常青树-单例模式
单例模式可谓是面试中的常青树,每当看到单例模式的七种写法时我总会想起孔乙己的茴字的四种写法。说正题,首先解决几个问题,什么是单例模式,为什么要用单例模式,单例模式解决的是啥问题呢。
1 单例模式
系统中存在一个类,但我们只想让它被实例化一次,即只有一个该类的对象。 比如系统中读取配置信息、线程池的管理等,比如现实中,沃尔玛可以当成一个类,全国大城市都有它的实例,但北京市市政府只能有一个,假如有好多个的话,你听谁的?所以单例模式是非常有必要的。
1.1 懒汉式 单线程可用
先来看代码
public class Singleton{
//将构造函数设为私有的 防止其他类实例化此类
private Singleton(){};
//将实例保存到私有变量
private static Singleton instance;
//获取实例 如果是null 就生成一个
public static Singleton getInstance(){
if( instance == null){
instance = new Singleton(); //第一处代码
}
return instance;
}
}
看完代码后发现,其他类无法实例化此类,当需要使用此实例时,使用Singleton.getInstance()即可获得实例,还是懒加载的,当第一次需要时才实例化,满足需求了吧?答案是否定的,这个在单线程环境下是可以的,多线程的话就不行了,假如不同的线程都调用了getInstance方法,因为没有同步,那么有可能出现都运行到第一处代码处,从而导致Singleton被实例化了两次。该如何改进呢?
1.2 懒汉式 简单同步
先看代码
public class Singleton{
private Singleton(){};
private static Singleton instance;
public synchronized static Singleton getInstance(){
if( instance == null){
instance = new Singleton();
}
return instance;
}
}
将getInstance方法变成同步方法,这样确实是线程安全了,但同时也导致性能问题,每次调用getInstance()方法时都会同步,而按道理说当已经创建了instance实例后,后续的获取instance实例全部是获取引用,不应该也同步。下面继续优化。
1.3 懒汉式 双重检验
public class Singleton{
private Singleton(){};
private Object locker = new Object();
private static volatile Singleton instance; //注意volatile关键字
public static Singleton getInstance(){
if( instance == null){
synchronized(locker){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
这种达到了按需生成的要求,同时性能上也很好,线程也是安全的,但是写起来比较复杂,容易写错。 这种方法基本已经废弃了,看看就好。下面在介绍几种。
1.4 饿汉式 静态变量
public class Singleton{
private Singleton(){}
private static final Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
Singleton类在加载时就会被初始化,优点是线程安全,简单,但没有做到按需加载,有没有既简单又线程安全还按需加载的呢?答案是肯定的。
1.5 饿汉式 静态内部类
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
private static class SingletonHolder{
public static final Singleton instance = new Singleton();
}
}
这种方法比较完美,简单,线程安全,还懒加载。为什么说比较完美而不是完美呢?因为这种可以通过反射来生成多个实例,同时如果需要添加序列化的,还需要提供一个readResolve方法来防止反序列化时生成新的实例。
下面介绍非常完美的方法,也是effective java推荐的,枚举。
1.6 枚举方式
public enum Singleton{
INSTANCE;
//具体的操作
public String getSomething(){
return "hello world";
}
public static void main(String[] args) {
System.err.println(Singleton.INSTANCE.getSomething());
}
}
线程安全,自带序列化(enum实现了serializable接口),可以防止反射攻击。
参考
超高校级的推理狂
自在时刻