【设计模式】- 单例模式[双检查(volatile)/静态内部类/枚举/登记式]


要点归纳

  • 单例模式
  • 使用场景
  • 实现方式
    • 饿汉式
    • 饱汉式
    • 双重检查
    • 静态内部类实现
    • 枚举实现
    • 登记式单例

单例模式

说起设计模式,耳熟能详恐怕就是单例模式。
这篇文章就整理下关于单例模式的实现方法,和使用场景。

单例模式:即保证该对象在jvm中,能够保证只有一个实例存在。
属于创建型模式,提供了一种创建对象的方式。

优点
由于只有一个实例,减小了系统中内存的开销。

缺点
扩展性能差,不能继承
不适用于需要变化的对象

关于 单例 or 多例 主要还是要看 适用的业务场景来选择

单例模式的实现规则
只能有一个该类的实例对象
需要具有提供该实例的功能


使用场景

几种常见适用单例模式的场景例子:

  1. Spring中Bean对象默认创建方式
  2. 读取配置文件资源的类对象
  3. 数据库连接对象
  4. 日志管理对象
    ··· 等等

饿汉式

public class SingletonTest {
        private SingletonTest() {}
        public static final SingletonTest instance = new SingletonTest();
        public SingletonTest getInstance() {
                return instance;
        }
}

优点:
1、线程安全
2、在类加载的同时已经创建好一个静态对象,调用时速度快
缺点:
不是一种懒加载模式,资源效率不高,如果getInstance()永远不会执行到,但类加载时就进行了初始化,浪费了内存。


饱汉式

class SingletonTest {
    public static SingletonTest instance;
    private SingletonTest() {}       
    public static SingletonTest getInstance() {          
    	if (instance == null) {
            	instance = new SingletonTest();
                }
           return instance;
     }
}

优点:
资源利用率高:避免了饿汉式的那种在没有用到的情况下创建事例,不执行getInstance()就不会被实例,可以执行该类的其他静态方法。
缺点:
线程不安全:多个线程同时访问的时候就可能同事创建多个实例,会存在拿到不同对象的情况。


双重检查

class SingletonTest {
        private SingletonTest() {
        }
        public volatile static SingletonTest instance = null;

        public static SingletonTest getInstance() {
            if (instance == null) {
                synchronized (Test.class) {
                    if (instance == null) {
                            instance = new SingletonTest();
                    }
                }
            }
           return instance;
        }
}

优点:
保证线程安全
资源利用率高:不执行getInstance()就不被实例,可以执行该类其他静态方法
instance 实例 是否需要用 volatile 修饰?

关于这个问题,需要知道
instance = new SingletonTest() 并不是原子形式完成的
其创建一个对象实例,分为三步:
1、分配对象内存
2、调用构造器方法,执行初始化
3、将对象引用赋值给变量。

问题原因:

JVM根据CPU的处理特性,在多核情况下会适当的进行指令重排序,从而最大程度上的提高性能。
·
比如 执行顺序 从原有的 [1- 2 - 3] 变为了 [1 - 3 - 2]
再此情况下:
1、线程1 执行 1 - 3 步骤,此时 instance 已被赋值了引用,此时不为空
2、线程2 进行 if (instance == null) 判断,不为空 则直接返回 instance对象
3、当调用 instance 时 就会 出现空指针异常

解决办法:

volatile 主要包含两个功能
1、保证可见性。
2、禁止指令重排序优化。(JDK1.5 +版本 )


静态内部类实现单例

public class SingletonTest {  
    private static class SingletonHolder {  
        private static final SingletonTest INSTANCE = new SingletonTest();  
    }  
    private SingletonTest (){}  
    public static final SingletonTest getInstance() {  
        return SingletonHolder.INSTANCE; 
    }  
}

优点
保证线程安全:由于类的 加载机制 < clinit >()
延时加载(懒加载):加载外部类时,并不会加载内部类


枚举实现单例

public enum SingletonEnum {

    INSTANCE;

    private UserBean bean = null;

    private SingletonEnum() {
        bean = new UserBean();
    }

    public UserBean getInstance() {
        return bean;
    }
} 
 public static void main(String[] args) {
        UserBean bean1 = SingletonEnum.INSTANCE.getInstance();
        UserBean bean2 = SingletonEnum.INSTANCE.getInstance();
        System.out.println(bean1 == bean2); // true
    }

JDK 1.5 提供了 枚举:
按照上面的例子 其实现 如下:

public final class SingletonEnum extends Enum<SingletonEnum> {
      public static final SingletonEnum INSTANCE;
      public static SingletonEnum[] values();
      public static SingletonEnum valueOf(String s);
      static {};
}

优点
1、线程安全
2、防止反射
3、避免序列化问题

可以看到 INSTANCE 被声明为 static,并且枚举类型变量在JVM中是唯一的。

利用 反射枚举类创建实例 调用会抛出异常:
java.lang.IllegalArgumentException: Cannot reflectively create enum objects


登记式单例

登记式实现单例,是利用Map作为登记簿维护实例,对于已经登记过的实例直接返回,对于没有进行登记的,则先登记然后再返回。

public class RegisterSingleton {

    /**
     * 使用map作为注册表,直接创建实例
     */
    private static Map<String, RegisterSingleton> map = new HashMap<String, RegisterSingleton>();

    /**
     * 声明为protected构造函数,用于子类访问
     */
    protected RegisterSingleton() {}

    /**
     * 判断是否已经存在namd对应实例,有则直接返回,没有新建登记再返回
     */
    public static RegisterSingleton getInstance(String name) {

        if (name == null) {
            name = RegisterSingleton.class.getName();
        }

        if (map.get(name) == null) {
            try {
                //登记name对应的实例
                map.put(name, (RegisterSingleton) Class.forName(name).newInstance());
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        //存在直接返回实例
        return map.get(name);
    }

    public Map<String, RegisterSingleton> getMap() {
        return map;
    }
} 

调用

public class Book extends RegisterSingleton {  
    static public Book getInstance() {  
    	return (Book) getInstance(Book.class.getName());  
    }  
}
public class User extends RegisterSingleton {  
    static public User getInstance() {  
    	return (User) getInstance(User.class.getName());  
    }  
}

该形式实现单例可继承;
Spring管理Bean就是利用登记式原理


为什么序列化 会 破坏单例模式?

当使用 ObjectInputputStream 的 readObject() 方式进行序列化时 会利用反射调用无参构造方法,从而创建一个新的对象。
在这里插入图片描述

以上内容,若有不足或错误,还望指正。欢迎探讨 ~

不止于前 未来可期 ···


在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值