什么是单例
单例是一个只实例化一次的类,即类仅存在一个对象。
私有化构造器 + 静态成员变量
显示声明构造器,并使用 private
关键字将其私有化,避免使用 new Object
来创建对象。需要使用该实例,只需要通过成员变量 INSTANCE 访问即可。
1 2 3 4
| public class MySingleton { public static final MySingleton INSTANCE = new MySingleton(); private MySingleton(){} }
|
缺点:会被 AccessibleObject.setAccessible 方法利用反射调用私有构造函数,实例化新的对象。
私有化构造器 + 静态工厂方法
该单例实现方法与上面基本一致,只需要将成员变量私有化,添加工厂方法来访问该成员变量即可。获取实例只需要调用 getInstance
即可。
1 2 3 4 5 6 7 8
| public class MySingleton { private static final MySingleton INSTANCE = new MySingleton(); private MySingleton(){} public static MySingleton getInstance(){ return INSTANCE; } }
|
缺点:同上。
实现序列化需要注意的问题
使用上述两种方法实现单例,且需要实现可序列化时,仅仅是实现 Serializable 接口是不够的,要维护单例保证,应声明所有实例字段为 transient,并提供 readResolve 方法(Item-89)。
否则,每次反序列化实例时,都会创建一个新实例,在我们的示例中,这会导致出现虚假的 MySingleton。为了防止这种情况发生,将这个 readResolve 方法添加到 MySingleton 类中。
详细的解决方法可以查看《Effective Java》第三版 Item 89。
单元素枚举
优点:默认提供了序列化机制,提供了对多个实例化的严格保证,即使面对复杂的序列化或反射攻击也是如此。
1 2 3
| public enum MySingleton{ INSTANCE; }
|
缺点:当类需要扩展超类时,enum 便不在适合。
延迟加载单例(双重检查不成立?)
延迟加载即只有第一次调用时,才进行初始化。
1 2 3 4 5 6 7 8 9 10 11
| public class MySingleton { private static MySingleton INSTANCE = null; private MySingleton(){} public static MySingleton getInstance(){ if (INSTANCE == null) { INSTANCE = new MySingleton(); } return INSTANCE; } }
|
当程序第一次调用 getInstance
方法时,MySingleton 实例才被初始化。
该延迟加载在多线程情况下,可能会创建多个实例,因此需要对 getInstance
方法进行加锁处理。
1 2 3 4 5 6
| public synchronized static MySingleton getInstance(){ if (INSTANCE == null) { INSTANCE = new MySingleton(); } return INSTANCE; }
|
加锁的意图在于第一次初始化的时候,保证只有一条线程进入到 if 语句中,生成实例。直接在方法上加锁,会导致性能下降。因为每次去取该实例都要去获得锁,所以需要对该方式进行优化。
1 2 3 4 5 6 7 8 9 10
| public static MySingleton getInstance(){ if (INSTANCE == null) { synchronized (MySingleton.class){ if (INSTANCE == null) { INSTANCE = new MySingleton(); } } } return INSTANCE; }
|
该方法称作 双重检查
,保证了多线程环境下,只生成一个实例,且后续对该实例的调用都不会进行加锁处理。
由于类加载以及对象初始化的特殊性值,会导致该双重检查不成立,可以使用 volatile 关键字声明变量,防止指令重排,实现最终目的。想了解该问题具体产生的原因,可以查看。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class MySingleton { private static volatile MySingleton INSTANCE = null; private MySingleton(){} public static MySingleton getInstance(){ if (INSTANCE == null) { synchronized (MySingleton.class){ if (INSTANCE == null) { INSTANCE = new MySingleton(); } } } return INSTANCE; } }
|
一份来自 Java 巨佬关于 双重检查
的阐述,点击这里 。