美文网首页Android开发Android技术知识程序员
设计模式-单例设计模式以及volatile关键字

设计模式-单例设计模式以及volatile关键字

作者: 请叫我张懂 | 来源:发表于2018-01-12 09:58 被阅读57次

单例设计模式的定义

在内存中只有一个对象实例

使用套路

  • 构造方法私有化
  • 使用静态方法,供外部获取对象的实例
1.饿汉式

HungrySingleton.java

public class HungrySingleton {
    private static HungrySingleton mInstance = new HungrySingleton();

    /**
     * 构造方法私有化
     */
    private HungrySingleton() {

    }
    
    public static HungrySingleton getInstance() {
        return mInstance;
    }
}

特点: 在类装载的时候,就已经创建实例,而且保证了线程的安全。(使用空间来节约时间,无论有没有使用到该对象,内存中都存在对象的实例)。

2.1懒汉式(存在线程安全问题)

LazySingleton.java

public class LazySingleton {
    private static LazySingleton mInstance;

    /**
     * 构造方法私有化
     */
    private LazySingleton() {

    }

    public static LazySingleton getInstance() {
        if (null == mInstance) {
            mInstance = new LazySingleton();
        }
        return mInstance;
    }
}

特点: 在对象被使用的时候才创建实例,但是存在线程安全问题。(使用时间来节约空间,每次进来都要判断实例是否为null,这个判断有一定的开销)

线程安全问题: 单线程时当然不存在任何问题,但是有多个线程进行调用时,就会存在并发问题。如下面一段代码,使用Set数据结构来存放单例的对象。

Set的特点:
  • 它不允许出现重复元素;

  • 不保证集合中元素的顺序

  • 允许包含值为null的元素,但最多只能有一个null元素。

TestDemo.java

public class TestDemo {
    public static Set<LazySingleton> singles = new HashSet<>();

    public static void main(String[] args) {
        SingletonRunnable runnable = new SingletonRunnable();
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
        
        ...
        
        //等待线程完成
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(singles);
    }

    private static class SingletonRunnable implements Runnable {
        @Override
        public void run() {
            LazySingleton s = LazySingleton.getInstance();
            singles.add(s);
        }
    }
}

运行的结果:

懒汉线程安全1.png 懒汉线程安全2.png

如图所示,图1 的情况是在多次运行的时候出现的,图2 的情况是正常的情况。说明,LazySingleton.java出现了线程并发访问导致的线程安全问题。所以在使用时要加上 synchronized 同步锁进行解决。

2.2懒汉式(解决线程安全问题)

LazySingleton.java

public class LazySingleton {

    private static LazySingleton mInstance;

    /**
     * 构造方法私有化
     */
    private LazySingleton() {

    }

    public static synchronized LazySingleton getInstance() {
        if (null == mInstance) {
            mInstance = new LazySingleton();
        }
        return mInstance;
    }
}

特点: 多线程并发访问的时候,每个线程获取实例都要进行同步锁的判断,效率较低。

2.3懒汉式(解决线程安全问题,效率较高)

LazySingleton.java

public class LazySingleton {
    private static LazySingleton mInstance;

    /**
     * 构造方法私有化
     */
    private LazySingleton() {

    }

    public static LazySingleton getInstance() {
        if (null == mInstance) {
            synchronized (LazySingleton.class) {
                if (null == mInstance) {
                    mInstance = new LazySingleton();
                }
            }
        }
        return mInstance;
    }
}

特点: 当 mInstance 不为空的时候则无须加上同步锁,保证的效率;当 mInstance 为空的时候加上同步锁,并且再次判空。

疑问: 为什么要进行两次判空呢?

只进行单次判空,并且执行 TestDemo.java 的情况如下:

 public class LazySingleton {
    private static LazySingleton mInstance;

    /**
     * 构造方法私有化
     */
    private LazySingleton() {

    }

    public static LazySingleton getInstance() {
        if (null == mInstance) {
            synchronized (LazySingleton.class) {
                mInstance = new LazySingleton();
            }
        }
        return mInstance;
    }
}

运行结果:

懒汉线程安全3.png 懒汉线程安全4.png

很明显如果少了第二层的 if (null == mInstance) 其实就与没有加 synchronized 关键字的代码是相似的。假设有线程1和线程2两个线程,一同走到了第一层的 null == mInstance ,一同进入的 if 里,在 synchronized (LazySingleton.class) 前排队,虽然线程安全,但是线程1和线程2都会执行 mInstance = new LazySingleton() 对象就不是唯一的了。

2.4懒汉式(加入volatile关键字)

并发编程有三个特性:

  • 原子性
  • 可见性
  • 有序性

volatile:

  1. 可以提供线程共享变量的可见性(体现了并发编程可见性)
  2. 禁止指令重排序(体现了并发编程的有序性)

首先,我们先理解一下 mInstance = new LazySingleton();在实例化对象的时候执行了下面三个步骤:

  1. mInstance分配内存
  2. 使用LazySingleton的构造函数初始化成员变量
  3. mInstance对象指向分配的内存空间(执行完mInstance就为不等于null

可以看出new对象的操作并非原子操作,所以编译器和处理器会对指令进行重排序。正常的顺应该为1->2->3,但是重排序之后,可能会将顺序变为1->3->2。假设按1->3->2的步骤进行,在线程1的中走到了3这个时候 mInstance != null 了,只是没有执行构造方法还未完成实例化。同时线程2 调用getInstance() 的时候就会返回 mInstance 对象,从而造成不必要的错误。但是如果加入了 volatile 就可以禁止指令重排序,不会出现上面的那种情况。

LazySingleton.java

public class LazySingleton {
    private static volatile LazySingleton mInstance;

    /**
     * 构造方法私有化
     */
    private LazySingleton() {

    }

    public static LazySingleton getInstance() {
        if (null == mInstance) {
            synchronized (LazySingleton.class) {
                if (null == mInstance) {
                    mInstance = new LazySingleton();
                }
            }
        }
        return mInstance;
    }
}
3静态内部类(推荐写法)

Singleton.java

public class Singleton {

    private Singleton() {
    }

    public static Singleton getInstance() {
        return SingletonHolder.mInstance;
    }

    private static class SingletonHolder {
        private static final Singleton mInstance = new Singleton();
    }
}

特点:这样做既保证了线程安全,也提高了效率,是一个高性能的懒加载。推荐使用该方法来实现单例。

验证例子:

静态内部类1.png 静态内部类2.png

如上图所示,实例化对象是在getInstance之后的。所以,通过静态内部类的使用,让JVM来保证线程的安全,减少了锁增加的耗时,并且是一个懒加载的模式。

相关文章

  • java 安全的单例设计模式之volatile

    通过double-checked locking单例模式 + volatile关键字实现安全的单例设计模式 pri...

  • 设计模式-单例设计模式以及volatile关键字

    单例设计模式的定义 在内存中只有一个对象实例 使用套路 构造方法私有化 使用静态方法,供外部获取对象的实例 1.饿...

  • 单例模式Java篇

    单例设计模式- 饿汉式 单例设计模式 - 懒汉式 单例设计模式 - 懒汉式 - 多线程并发 单例设计模式 - 懒汉...

  • python中OOP的单例

    目录 单例设计模式 __new__ 方法 Python 中的单例 01. 单例设计模式 设计模式设计模式 是 前人...

  • 单例

    目标 单例设计模式 __new__ 方法 Python 中的单例 01. 单例设计模式 设计模式设计模式 是 前人...

  • 设计模式

    常用的设计模式有,单例设计模式、观察者设计模式、工厂设计模式、装饰设计模式、代理设计模式,模板设计模式等等。 单例...

  • python 单例

    仅用学习参考 目标 单例设计模式 __new__ 方法 Python 中的单例 01. 单例设计模式 设计模式设计...

  • 设计模式之单例模式

    单例设计模式全解析 在学习设计模式时,单例设计模式应该是学习的第一个设计模式,单例设计模式也是“公认”最简单的设计...

  • 面试题

    非作者原著 来自摘抄 yahoouchen 设计模式 MVC模式 MVVM模式 单例模式: 通过static关键字...

  • 2、创建型设计模式-单例设计模式

    江湖传言里的设计模式-单例设计模式 简介:什么是单例设计模式和应用 备注:面试重点考查 单例设计模式:这个是最简单...

网友评论

    本文标题:设计模式-单例设计模式以及volatile关键字

    本文链接:https://www.haomeiwen.com/subject/gmiqnxtx.html