zoukankan      html  css  js  c++  java
  • 设计模式之单例模式

    单例模式

    作为对象的创建模式,单例模式确保某一个类只有一个实例,且自行实例化并向系统提供这个实例,这个类称为单例类。

    单例模式的特点:

    1. 单例类只有一个实例

    2. 单例类必须自己创建自己的唯一实例

    3. 单例类必须给所有对象提供这一实例

    饿汉式

     1 class Instance1 {
     2     private static Instance1 instance1 = new Instance1();
     3 
     4     private Instance1() {
     5 
     6     }
     7 
     8     public static Instance1 getInstance() {
     9         return instance1;
    10     }
    11 }

    饿汉式,就是使用类的时候不管用的是不是类的单例部分,都重新创建一个单例类。这种写法不会造成竞争,线程安全。

    为什么饿汉式单例天生就是线程安全的?

    类的加载方式是按需加载,且只会加载一次。因此,单例类被加载时,就会实例化一个对象并交给自己的引用,供系统使用。换句话说就是,在线程访问单例对象之前,就已经创建好了对象。再加上一个类在整个生命周期中,只会被加载一次,所以只会创建一个实例。线程每次只能也必定能拿到这个唯一的对象。所以说是线程安全的。

    懒汉式(非线程安全)

     1 class Instance2 {
     2     private static Instance2 instance = null;
     3 
     4     private Instance2() {
     5 
     6     }
     7 
     8     public static Instance2 getInstance() {
     9         if (instance == null) {
    10             instance = new Instance2();
    11         }
    12         return instance;
    13     }
    14 
    15 }

    懒汉式,就是只有当单例类用到时,才会创建。这种写法基本不用,因为它是线程不安全的。多个线程在第9行判断是否为空时,就会出现问题。

    双重检查

     1 class Instance3 {
     2     private volatile static Instance3 instance = null;
     3 
     4     private Instance3() {
     5 
     6     }
     7 
     8     public static Instance3 getInstance() {
     9         if (instance == null) {
    10             synchronized (Instance3.class) {
    11                 if (instance == null) {
    12                     instance = new Instance3();
    13                 }
    14             }
    15         }
    16         return instance;
    17     }
    18 }

    最简单的方法是直接给getInstance()方法加锁,但这样效率低。线程调用getInstance()方法都会等待获取锁。事实上,只有第一次创建对象时需要加锁,其他时候都不需要。基于这个考虑,所以引用了双检锁(double checked lock)的写法。

    但是上面的instance变量如果没有加volatile关键字,就会出现问题。

    首先我们要明白的是new Instance3()是一个非原子操作,分为3步:

    1. 给对象分配内存空间

    2. 初始化对象

    3. 将变量指向刚分配的内存地址

    但实际上,这个过程可能会发生无序写入(指令重排序),也就是说上面的3条指令可能会因为指令重排序而先执行步骤3再执行步骤2。

    这样可能会发生如下步骤:

    1. 线程1进入getInstance()方法,判断instance==null,进入synchronized代码块
    2. 线程1被线程2预占,因为instance为null,也想进入synchronized代码块,但线程1持有锁对象,线程2只能阻塞
    3. 线程1执行,new对象,退出同步代码块,并返回实例instance
    4. 线程2获得了锁对象,执行到第二个instance==null时,因为已经不等于null了,所以直接退出,返回instance(但可能该实例还没有初始化或完成初始化,导致线程2使用该实例而出错)

    所以volatile关键字是必须的

    静态内部类

     1 class Instance4 {
     2 
     3     private Instance4() {
     4         
     5     }
     6 
     7     private static class InstanceHolder {
     8         private static Instance4 instance = new Instance4();
     9     }
    10 
    11     public static Instance4 getInstance() {
    12         return InstanceHolder.instance;
    13     }
    14 }

    这种方式依然使用classloder机制,使得单例类仍然只会被加载一次,不同的是只有当调用getInstance方法时,才会显示装在类InstanceHolder,从而实例化Instance4。

    枚举

    1 enum Instance5 {
    2     instance;//实例
    3 
    4     public void method() {
    5         ...6     }
    7 
    8 }

    直接用Instance5.instance.method()调用方法。使用枚举不仅能避免多线程同步问题,还能防止反序列化重新创建对象。

    单例模式的好处

    1. 控制资源的使用,通过多线程同步来控制资源的并发访问

    2. 控制实例的产生,从而节约资源

    3. 控制数据的共享,在不建立直接关联的情况下,让多个不相关的进程或线程实现通信

  • 相关阅读:
    P2056 [ZJOI2007]捉迷藏
    P2993 [FJOI2014]最短路径树问题
    P4149 [IOI2011]Race 点分治
    P2634 [国家集训队]聪聪可可 点分治
    [APIO2008]免费道路
    [luogu4255]公主の#18文明游戏
    [ZJOI2010]基站选址
    [POI2011]Meteors
    [SCOI2015]国旗计划
    [BZOJ4373]算术天才⑨与等差数列
  • 原文地址:https://www.cnblogs.com/yushangzuiyue/p/8659669.html
Copyright © 2011-2022 走看看