单例(Singleton)
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
在GoF23种设计模式中,属于创建型模式( Creational patterns)
。
现实生活中很多东西都是唯一的。比如:地球,太阳等。
对应代码中,如果我们要使用地球这个对象,获取地球的水,石油等资源时,地球这个对象就应该设计成单例模式。
-
优势
- 节省内存开销
- 避免对资源进行多重占用
- 严格控制访问,外部不能new对象
-
缺点
- 难拓展,无接口
名词解释
在进行实际编码之前,我们应该知道几个名词的含义。
延迟加载:仅在使用到数据时,才进行数据的加载。这样可以加快程序的加载速度,同时可以节省一些不必要的资源占用。
线程安全:当多个线程访问某个类时,不管运行时环境采用何种调用方式或者这些线程如何交替执行,并且在主调代码中不需要任何额外的同步或协调,这个类都能表现出正确的行为。
一图以蔽之
coding 演进
懒汉式
延迟加载:是
线程安全:否
public class LazySingleton {
private static LazySingleton lazySingleton = null;
public static LazySingleton getInstance(){
if (lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
/**
* 私有化构造器
*/
private LazySingleton() {
}
}
懒汉式最大的缺点就是线程不安全。
假设有两个线程A、B。A线程在执行lazySingleton = new LazySingleton();
时,进入阻塞状态,此时lazySingleton
仍为null,因此,B线程仍能进入执行lazySingleton = new LazySingleton();
。
此时,A、B线程得到的对象就不是同一个了,这显然违反了单例的基本定义。
为了解决线程安全问题,则需加同步的关键字。
懒汉式-线程安全
延迟加载:是
线程安全:是
public class LazySingleton {
private static LazySingleton lazySingleton = null;
public static synchronized LazySingleton getInstance() {
if (lazySingleton == null) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
/**
* 私有化构造器
*/
private LazySingleton() {
}
}
通过这种同步锁机制虽然解决了线程安全问题,但synchronized
本身就需要一定的资源开销,另外synchronized
锁的是静态方法,其就是锁的LazySingleton.class
,锁的范围过大,性能不佳。
为了提供性能,于是有了Double-Check
式。
双重检查
延迟加载:是
线程安全:是
public class DoubleCheckSingleton {
private static DoubleCheckSingleton doubleCheckSingleton = null;
private DoubleCheckSingleton() {
}
public static DoubleCheckSingleton getInstance() {
if (doubleCheckSingleton == null) {
synchronized (DoubleCheckSingleton.class) {
if (doubleCheckSingleton == null) {
//下面new对象时有重排序问题
doubleCheckSingleton = new DoubleCheckSingleton();
}
}
}
return doubleCheckSingleton;
}
}
双重检查,这里把锁的范围从类锁,降为方法锁,提升了系统性能。同时也兼具延迟加载和线程安全。
但是,这样写会有指令重排序问题。
new DoubleCheckSingleton()时会进行几个操作:
1、在堆区分配对象需要的内存
2、初始化对象
3、将堆区对象的地址赋值给doubleCheckSingleton
这里的2、3操作可能重排序,按1、3、2顺序执行。
此时,doubleCheckSingleton
已经不为null,但对象并未创建,从而引起错误。
为了避免重排序的发生可以采用以下两种方式:
1、禁止重排序
2、屏蔽重排序
禁止重排序-双重检查
延迟加载:是
线程安全:是
public class DoubleCheckSingleton {
private static volatile DoubleCheckSingleton doubleCheckSingleton = null;
private DoubleCheckSingleton() {
}
public static DoubleCheckSingleton getInstance() {
if (doubleCheckSingleton == null) {
synchronized (DoubleCheckSingleton.class) {
if (doubleCheckSingleton == null) {
//下面new对象时有重排序问题
doubleCheckSingleton = new DoubleCheckSingleton();
}
}
}
return doubleCheckSingleton;
}
}
使用volatile
关键字可以禁止重排序问题。至此,Double-Check 已进化至究极体。
屏蔽重排序-静态内部类
延迟加载:是
线程安全:是
public class StaticInnerClassSingleton {
private static class Instance{
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance(){
return Instance.staticInnerClassSingleton;
}
private StaticInnerClassSingleton() {
}
}
因为内部类和静态内部类都是延时加载的,也就是说只有在明确用到内部类时才加载。只使用外部类时不加载。
故,只有在调用getInstance()
时,StaticInnerClassSingleton.class才会被初始化。
且,在执行类的初始化期间,JVM会去获取一个锁,该锁可以同步多个线程对同一个类的初始化。从而屏蔽了静态内部类重排序对外部的影响。
饿汉式
延迟加载:否
线程安全:是
public class HungrySingleton{
private final static HungrySingleton HUNGRY_SINGLETON;
static {
HUNGRY_SINGLETON = new HungrySingleton();
}
public static HungrySingleton getInstance() {
return HUNGRY_SINGLETON;
}
private HungrySingleton() {
}
}
这种模式虽然简单易实现,但非延迟加载。
枚举类单例
延迟加载:否
线程安全:是
public enum EnumSingleton {
INSTANCE;
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
这么写我们很难看出枚举类是怎么实现单例的,是因为枚举类用了java的语法糖。
接下来让我们用jad反编译下这个类的class,看下正真的运行代码的样子。
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: EnumSingleton.java
package top.ordin.tool.design.pattern.creational.singleton;
public final class EnumSingleton extends Enum
{
public static EnumSingleton[] values()
{
return (EnumSingleton[])$VALUES.clone();
}
public static EnumSingleton valueOf(String name)
{
return (EnumSingleton)Enum.valueOf(top/ordin/tool/design/pattern/creational/singleton/EnumSingleton, name);
}
private EnumSingleton(String s, int i)
{
super(s, i);
}
public static EnumSingleton getInstance()
{
return INSTANCE;
}
public static final EnumSingleton INSTANCE;
private static final EnumSingleton $VALUES[];
static
{
INSTANCE = new EnumSingleton("INSTANCE", 0);
$VALUES = (new EnumSingleton[] {
INSTANCE
});
}
}
通过反编译,我们不难看出单例自己实现了私有构造器,而且还通过静态代码块初始化了类对象。
这种语法糖包装后的枚举类跟饿汉式很像。
但由于这种写法出现较晚,所以日常很少见,但保不定这种方式将会成为主流。
单例序列化
序列化时,对枚举类型做了特殊处理,使得枚举类在序列化与反序列化后仍能保持单例。
其他类型的单例模式就没这待遇了,但是序列化仍保留了一个重写的读方法。其他单例类型通过添加该方法仍能保持单例不被打破。
private Object readResolve(){
return INSTANCE;
}
单例防止反射
java可以通过反射的方式创建对象。
但是,在反射创建时,会对枚举类做特殊处理,禁止反射创建枚举对象。
测试代码
public static void main(String[] args) throws Exception {
//反射创建
Class enumInstanceClass = EnumInstance.class;
Constructor constructor = enumInstanceClass.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
EnumInstance instance = (EnumInstance) constructor.newInstance("demo", 123);
EnumInstance instance1 = EnumInstance.getInstance();
System.out.println(instance == instance1);
}
output
Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
at com.geely.design.pattern.creational.singleton.Test.main(Test.java:53)
饿汉式和静态内部类因为在类初始化时就被加载。因此可以在构造器中添加判断条件,如果对象已被创建就抛出异常,以此来防止反射调用。
private HungrySingleton(){
if(HUNGRY_SINGLETON != null){
throw new RuntimeException("Cannot reflectively create this objects");
}
}
private StaticInnerClassSingleton() {
if (Instance.staticInnerClassSingleton != null){
throw new RuntimeException("Cannot reflectively create this objects");
}
}
懒汉式就放弃吧...
推荐单例写法
综上所述,一般比较推荐静态内部类和枚举类写法。
静态内部类
public class StaticInnerClassSingleton {
private static class Instance{
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance(){
return Instance.staticInnerClassSingleton;
}
private StaticInnerClassSingleton() {
if (Instance.staticInnerClassSingleton != null){
throw new RuntimeException("Cannot reflectively create this objects");
}
}
private Object readResolve(){
return Instance.staticInnerClassSingleton;
}
}
枚举类
public enum EnumSingleton {
INSTANCE;
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名,转载请标明出处
最后编辑时间为:
2019-10-25