zoukankan      html  css  js  c++  java
  • 单例模式介绍及其线程安全问题

     

        介绍下单例模式,即保证对一个类只实例化一个对象。实际生产例子有,Spring的bean默认创建模式等。

      单例模式的组成:包括一个私有的构造器,一个私有的静态变量,一个公有的静态方法。单例模式本身很简单,主要复杂点是在它在线程并发下的如何保证 线程安全+资源消耗少 的问题。

     

      一.饿汉式单例(线程安全)

     

    缺点:直接实例化,资源会浪费。丢失了延迟实例化的性能好处。

     

     二.懒汉式单例(线程不安全)

     

     缺点:线程不安全,如果多个线程能够同时进入 if (instance == null) ,并且此时 instance 为 null,那么会有多个线程执行 instance = new LazySingleton(); 语句,这将导致实例化多次对象。即此类不是单例了。

     

    三.线程安全的懒汉式单例例子。

      1>  对 getInstance() 方法加锁,那么在一个时间点只能有一个线程能够进入该方法,从而避免了实例化多次 uniqueInstance。

    缺点:会有线程堵塞,性能上不好,不推荐。

     

     

     2>  双重校验锁。先判断 instance 是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁

    缺点:基本无缺点,除了第一次实例化的时候会加锁,可能会有线程堵塞。

      如果两个线程都执行了 if 语句,那么两个线程都会进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行instance = new Singleton(); 这条语句,只是先后的问题,那么就会进行两次实例化。因此必须使用双重校验锁,也就是需要使用两个 if 语句。

      instance 采用 volatile 关键字修饰也是很有必要的。volatile的作用是 1.内存可见性: 所有线程都能看到共享内存的最新状态  2.防止指令重排。 (volatile修饰的变量并不是原子变量。只是变量的读和写变成了原子操作)

       instance = new LazySingleton(); 这段代码其实是分为三步执行:

    1. 为 instance 分配内存空间
    2. 初始化 instance 
    3. 将 instance 指向分配的内存地址

    但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1>3>2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致一个线程获得还没有初始化的实例。例如,线程 T1 执行了 1 和 3,此时 T2 调用 getInstance() 后发现 instance 不为空,因此返回 instance ,但此时 instance 还未被初始化。

    使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。

     

     

     3>静态内部类实现。具有延迟初始化的好处,而且由 JVM 提供了对线程安全的支持。

    内部静态类的变量并不会在类加载的时候就初始化好,而是会在调用的时候才初始化。保证资源的节约。

     

  • 相关阅读:
    Apache 的 ab 压测工具快速使用
    Go_22: Golang 命令行 test 应用
    Go_21: Golang 中 time 包的使用二
    ElasticStack系列之十八 & ElasticSearch5.x XPack 过期新 License 更新
    Go 语言编程规范
    ElasticStack系列之十七 & 大文本搜索性能提升方案
    ElasticStack系列之十六 & ElasticSearch5.x index/create 和 update 源码分析
    ElasticStack系列之十五 & query cache 引起性能问题思考
    golang 配置文件读取
    pandoc安装
  • 原文地址:https://www.cnblogs.com/liumz0323/p/10424789.html
Copyright © 2011-2022 走看看