大厂面试之JAVA核心技能:Slipped Conditions

2023-05-16

聊聊Splipped Condtion

  • 定义
  • 一个更现实的例子
  • 解决Slipped Conditions问题

定义

所谓Slipped conditions,就是说, 从一个线程检查某一特定条件到该线程操作此条件期间,这个条件已经被其它线程改变,导致第一个线程在该条件上执行了错误的操作。这里有一个简单的例子:

public class Lock {
    private boolean isLocked = true;=
    public void lock(){
      synchronized(this){
        while(isLocked){
          try{
            this.wait();
          } catch(InterruptedException e){
            //do nothing, keep waiting
          }
        }
      }
 
      synchronized(this){
        isLocked = true;
      }
    }

    public synchronized void unlock(){
      isLocked = false;
      this.notify();
    }
}

我们可以看到,lock()方法包含了两个同步块。第一个同步块执行wait操作直到isLocked变为false才退出,第二个同步块将isLocked置为true,以此来锁住这个Lock实例避免其它线程通过lock()方法。

我们可以设想一下,假如在某个时刻isLocked为false, 这个时候,有两个线程同时访问lock方法。如果第一个线程先进入第一个同步块,这个时候它会发现isLocked为false,若此时允许第二个线程执行,它也进入第一个同步块,同样发现isLocked是false。现在两个线程都检查了这个条件为false,然后它们都会继续进入第二个同步块中并设置isLocked为true。

这个场景就是slipped conditions的例子,两个线程检查同一个条件, 然后退出同步块,因此在这两个线程改变条件之前,就允许其它线程来检查这个条件。换句话说,条件被某个线程检查到该条件被此线程改变期间,这个条件已经被其它线程改变过了。

为避免slipped conditions,条件的检查与设置必须是原子的,也就是说,在第一个线程检查和设置条件期间,不会有其它线程检查这个条件。

解决上面问题的方法很简单,只是简单的把isLocked = true这行代码移到第一个同步块中,放在while循环后面即可:

public class Lock {
    private boolean isLocked = true;
    public void lock(){
      synchronized(this){
        while(isLocked){
          try{
            this.wait();
          } catch(InterruptedException e){
            //do nothing, keep waiting
          }
        }
        isLocked = true;
      }
    }
    public synchronized void unlock(){
      isLocked = false;
      this.notify();
    }
}

现在检查和设置isLocked条件是在同一个同步块中原子地执行了。

一个更现实的例子

也许你会说,我才不可能写这么挫的代码,还觉得slipped conditions是个相当理论的问题。但是第一个简单的例子只是用来更好的展示slipped conditions。

饥饿和公平中实现的公平锁也许是个更现实的例子。再看下嵌套管程锁死中那个幼稚的实现,如果我们试图解决其中的嵌套管程锁死问题,很容易产生slipped conditions问题。 首先让我们看下嵌套管程锁死中的例子:


//Fair Lock implementation with nested monitor lockout problem
public class FairLock {
  private boolean isLocked = false;
  private Thread lockingThread = null;
  private List waitingThreads =
            new ArrayList();
  public void lock() throws InterruptedException{
    QueueObject queueObject = new QueueObject();
    synchronized(this){
      waitingThreads.add(queueObject);
      while(isLocked || waitingThreads.get(0) != queueObject){
        synchronized(queueObject){
          try{
            queueObject.wait();
          }catch(InterruptedException e){
            waitingThreads.remove(queueObject);
            throw e;
          }
        }
      }
      waitingThreads.remove(queueObject);
      isLocked = true;
      lockingThread = Thread.currentThread();
    }
  }
  public synchronized void unlock(){
    if(this.lockingThread != Thread.currentThread()){
      throw new IllegalMonitorStateException(
        "Calling thread has not locked this lock");
    }
    isLocked      = false;
    lockingThread = null;
    if(waitingThreads.size() > 0){
      QueueObject queueObject = waitingThread.get(0);
      synchronized(queueObject){
        queueObject.notify();
      }
    }
  }
}
public class QueueObject {}

我们可以看到synchronized(queueObject)及其中的queueObject.wait()调用是嵌在synchronized(this)块里面的,这会导致嵌套管程锁死问题。为避免这个问题,我们必须将synchronized(queueObject)块移出synchronized(this)块。移出来之后的代码可能是这样的:

//Fair Lock implementation with slipped conditions problem
public class FairLock {
  private boolean isLocked = false;
  private Thread lockingThread  = null;
  private List waitingThreads =  new ArrayList();
  public void lock() throws InterruptedException{
    QueueObject queueObject = new QueueObject();
    synchronized(this){
      waitingThreads.add(queueObject);
    }
    boolean mustWait = true;
    while(mustWait){
      synchronized(this){
        mustWait = isLocked || waitingThreads.get(0) != queueObject;
      }
      synchronized(queueObject){
        if(mustWait){
          try{
            queueObject.wait();
          }catch(InterruptedException e){
            waitingThreads.remove(queueObject);
            throw e;
          }
        }
      }
    }
    synchronized(this){
      waitingThreads.remove(queueObject);
      isLocked = true;
      lockingThread = Thread.currentThread();
    }
  }
}

注意:因为我只改动了lock()方法,这里只展现了lock方法。

现在lock()方法包含了3个同步块。

第一个,synchronized(this)块通过mustWait = isLocked || waitingThreads.get(0) != queueObject检查内部变量的值。

第二个,synchronized(queueObject)块检查线程是否需要等待。也有可能其它线程在这个时候已经解锁了,但我们暂时不考虑这个问题。我们就假设这个锁处在解锁状态,所以线程会立马退出synchronized(queueObject)块。

第三个,synchronized(this)块只会在mustWait为false的时候执行。它将isLocked重新设回true,然后离开lock()方法。

设想一下,在锁处于解锁状态时,如果有两个线程同时调用lock()方法会发生什么。首先,线程1会检查到isLocked为false,然后线程2同样检查到isLocked为false。接着,它们都不会等待,都会去设置isLocked为true。这就是slipped conditions的一个最好的例子。

解决Slipped Conditions问题

要解决上面例子中的slipped conditions问题,最后一个synchronized(this)块中的代码必须向上移到第一个同步块中。为适应这种变动,代码需要做点小改动。下面是改动过的代码:

//Fair Lock implementation without nested monitor lockout problem,
//but with missed signals problem.
public class FairLock {
  private boolean isLocked = false;
  private Thread lockingThread  = null;
  private List waitingThreads =
            new ArrayList();
  public void lock() throws InterruptedException{
    QueueObject queueObject = new QueueObject();
    synchronized(this){
      waitingThreads.add(queueObject);
    }
    boolean mustWait = true;
    while(mustWait){
      synchronized(this){
        mustWait = isLocked || waitingThreads.get(0) != queueObject;
        if(!mustWait){
          waitingThreads.remove(queueObject);
          isLocked = true;
          lockingThread = Thread.currentThread();
          return;
        }
      }    
      synchronized(queueObject){
        if(mustWait){
          try{
            queueObject.wait();
          }catch(InterruptedException e){
            waitingThreads.remove(queueObject);
            throw e;
          }
        }
      }
    }
  }
}

我们可以看到对局部变量mustWait的检查与赋值是在同一个同步块中完成的。还可以看到,即使在synchronized(this)块外面检查了mustWait,在while(mustWait)子句中,mustWait变量从来没有在synchronized(this)同步块外被赋值。当一个线程检查到mustWait是false的时候,它将自动设置内部的条件(isLocked),所以其它线程再来检查这个条件的时候,它们就会发现这个条件的值现在为true了。

synchronized(this)块中的return;语句不是必须的。这只是个小小的优化。如果一个线程肯定不会等待(即mustWait为false),那么就没必要让它进入到synchronized(queueObject)同步块中和执行if(mustWait)子句了。

细心的读者可能会注意到上面的公平锁实现仍然有可能丢失信号。设想一下,当该FairLock实例处于锁定状态时,有个线程来调用lock()方法。执行完第一个 synchronized(this)块后,mustWait变量的值为true。再设想一下调用lock()的线程是通过抢占式的,拥有锁的那个线程那个线程此时调用了unlock()方法,但是看下之前的unlock()的实现你会发现,它调用了queueObject.notify()。但是,因为lock()中的线程还没有来得及调用queueObject.wait(),所以queueObject.notify()调用也就没有作用了,信号就丢失掉了。如果调用lock()的线程在另一个线程调用queueObject.notify()之后调用queueObject.wait(),这个线程会一直阻塞到其它线程调用unlock方法为止,但这永远也不会发生。

公平锁实现的信号丢失问题在饥饿和公平一文中我们已有过讨论,把QueueObject转变成一个信号量,并提供两个方法:doWait()和doNotify()。这些方法会在QueueObject内部对信号进行存储和响应。用这种方式,即使doNotify()在doWait()之前调用,信号也不会丢失。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

大厂面试之JAVA核心技能:Slipped Conditions 的相关文章

  • Ubuntu挂载分区

    1 查看本地分区 xff0c 找到你想要挂载的分区名称 sudo fdisk l 图中为以整块2T硬盘 xff0c 分为5 6 7 8 9区 xff0c 以 dev sdb7 为例 xff0c 想要将其挂载到 home plan other
  • 允许远程该计算机的其他用户

    当你想进入服务器的其他用户时 xff0c 发现通过administrator是无法直接切换用户的 xff0c 那么我们应该怎么设置呢 xff1f 解决方案 xff1a 1 右击计算机 gt 属性 gt 高级系统设置 gt 远程 xff0c
  • 学习Linux 编程的几本好书

    这次涉及到了具体的平台 GNU Linux Linux下开发与明显不同于Windows平台的特点 xff0c 从开发工具到项目组织 xff0c 都有较大的差距 首先声明 xff0c 在做Linux平台开发之前 xff0c 首先要熟练使用Li
  • IE8报错:Unable to modify the parent container element before the child element is closed

    xfeff xfeff 转自 xff1a http blog csdn net xinwang article details 9786447 IE8中会报 HTML Parsing Error Unable to modify the p
  • APNs 访问不到的问题

    APNs会将链接太频繁的链接视为DDos攻击 xff0c 所以链接频率不要太高 目前每5分钟连接接一次 因为使用了加密链接 xff0c 会被GFW随机阻断 看脸 看有的说建议用国外VPS 单个ip连接每次发送消息数量不要超过1000条 xf
  • 谷歌原生DocumentUI文件浏览的原理

    相信多数想了解谷歌DocumentUI设计思想的码农都会遇到障碍 xff0c 文件浏览究竟是怎么实现的 xff0c 进入DocumentUI的UI层 xff0c 不难发现 xff0c 我们是通过查询数据库获取cursor xff0c 但是查
  • linux下快速查找文件

    版权声明 xff1a 本文为博主xxt 测试开发之路的原创文章 xff0c 遵循 CC 4 0 BY SA 版权协议 xff0c 转载请附上原文出处链接和本声明 本文链接 xff1a https blog csdn net xxmonsto
  • Android 多语言对照表

    语言地区文件夹名称南非荷兰语南非values af rNA南非荷兰语纳米比亚values af rZA阿肯语加纳values ak rGH阿姆哈拉语埃塞俄比亚values am rET阿拉伯语阿拉伯联合酋长国values ar rAE阿拉伯
  • android图片轮播+点击跳转广告页面

    Android轮播网络图片 43 点击跳转广告页面 一些新手总是很头疼怎么获取网络图片的url之后让它像一些广告那样轮播起来 xff0c 点击图片之后跳转到指定网页 效果如下 在布局引用自定义控件 span class hljs pi lt
  • 用SurfaceView实现级联分层图(粗略篇)

    先看效果图 实际运行很流畅 xff0c 运行内存1M左右 最近脑抽 xff0c 想实现一个亲戚关系图谱的应用 xff0c 但始终没有找到合适的开源控件 xff0c 于是就看到一篇 利用递归算法和堆栈实现android思维导图大纲图的动态绘制
  • Android各种访问权限Permission详解

    在Android的设计中 xff0c 资源的访问或者网络连接 xff0c 要得到这些服务都需要声明其访问权限 xff0c 否则将无法正常工作 在Android中这样的权限有很多种 xff0c 这里将各类访问权限一一罗列出来 xff0c 供大
  • 桌面远程连接Ubuntu图形界面和开启ssh连接

    1 xff0c 设置Ubuntu为可被远程连接 Settings Sharing Screen Sharing Access Options设置一个远程连接的密码 连接远程是的密码 xff0c 区别于用户密码 2 xff0c 安装支持vnc
  • Android动态设置Shape

    有过一些开发经验的朋友 xff0c 在做圆角按钮的背景时可能不再需要 9的切图了 xff0c 而一般都是在drawable文件夹下面建立一个xml文件shape 其他状态不变色 或者selector 按下 选中状态变色 xff0c 但是如果
  • Android 禁止锁屏或黑屏

    转载请注明出处 xff1a http blog csdn net snailbaby soko article details 56842467 场景 xff1a 通常情况我们使用的 app 都不需要用到这个功能 但一些平板的开发就很常见了
  • Android 美团Robust热更新 使用入门

    转载请注明出处 http blog csdn net snailbaby soko article details 69524380 本篇文章已授权微信公众号 guolin blog xff08 郭霖 xff09 独家发布 Android热
  • Android 实战-版本更新(okhttp3、service、notification)

    转发请注明出处 http www jianshu com p b669940c9f3e 前言 整理功能 xff0c 把这块拿出来单独做个demo xff0c 好和大家分享交流一下 版本更新这个功能一般 app 都有实现 xff0c 而用户获
  • spark mllib源码分析之二分类逻辑回归的评价指标

    在逻辑回归分类中 xff0c 我们评价分类器好坏的主要指标有精准率 xff08 precision xff09 xff0c 召回率 xff08 recall xff09 xff0c F measure xff0c AUC等 xff0c 其中
  • AB升级之odex文件首次开机处理

    开启AB升级方案的项目 xff0c 因为很多需要升级的镜像都有两份 xff0c 所以存储空间比较浪费 为缓解此问题 xff0c 有个针对odex的优化方案 编译版本会生成两个system镜像 xff1a system img和system
  • Android Bluetooth HCI log 详解

    0 引子 对于蓝牙开发者来说 xff0c 通过HCI log可以帮助我们更好地分析问题 xff0c 理解蓝牙协议 xff0c 就好像网络开发一定要会使用Wireshark分析网络协议一样 本篇主要介绍HCI log的作用 如何抓取一份HCI
  • imx6ull开发板调试nfs环境配置+运行hello程序

    20210314 43 imx6ull开发板nfs环境配置 1 设置git邮箱和用户名 wang 64 wang virtual machine git config global user name 34 snaking616 34 wa

随机推荐

  • 网络编程6:线程池简介

    1 线程池相关结构体 struct threadpool t pthread mutex t lock 用于锁住本结构体 pthread mutex t thread counter 记录忙状态线程个数de琐 busy thr num pt
  • 网络编程7:本地套接字

    1 基于UDP的网络编程 1 1 TCP通信和UDP通信各自的优缺点 TCP xff1a 面向连接的 xff0c 可靠数据包传输 对于不稳定的网络层 xff0c 采取完全弥补的通信方式 丢包重传 优点 xff1a 稳定 数据流量稳定 速度稳
  • Effectinve Python的59个有效方法中第38条使用Lock防止数据竞争中运行错误的问题

    系统 xff1a Ubuntu1804 在Jupyter Notebook中运行 xff0c 使用的anaconda下的python3 7 6 在代码运行中出现错误的问题 xff0c 源代码如下 xff1a 代码为书中样例代码 xff0c
  • 4相直流步进电机工作原理+温度PID算法

    四相步进电机原理图 https tech hqew com circuit 511266 步进电机 xff08 四相五线为例子 xff09 步进角度和工作原理介绍 https blog csdn net qq 34824576 articl
  • imx6ull-mini开发板调试环境汇总

    一 安装USB驱动 相关页面 xff1a https www silabs com developers usb to uart bridge vcp drivers 下载链接 xff1a CP210x Universal Windows
  • OrCAD Capture CIS的使用方法

    软件版本 xff1a Cadence allegro 16 5 参考教程 xff1a 于争博士 Cadence视频教程 第1讲 课程介绍 xff0c 学习方法 xff0c 了解CADENCE软件 第2讲 创建工程 xff0c 创建元件库 主
  • PCI-E 1x, 4x, 8x, 16x 接口定义

    1 PCI E插槽及金手指实物图 xff08 1 xff09 PCI E插槽 从上至下依次为PCI E 4X PCI E 16X PCI E 1X xff08 2 xff09 PCI E金手指 PCI E 1X金手指PCI E 4X金手指P
  • Lattice USB下载线使用说明及CPLD程序烧写

    目录 1 下载并安装ispLEVER Classic 软件 1 1 软件下载 1 2 软件的安装 2 Lattice USB下载线介绍 2 1 实物介绍
  • E-96系列电阻值代号对照表

    1 说明 2 E 96系列电阻值代号对照表 E 96系列 标准阻值表阻值代码阻值代码阻值代码阻值代码阻值代码阻值代码1001X10001A1 00K01B10 0K01C100K01D1M01E10 202X10202A1 02K02B10
  • Altium Designer10铺铜技巧小结

    目录 1 铜皮操作分类 2 铺铜技巧 2 1 过孔处理 2 1 1 过孔与绿油 2 1 2 过孔的十字连接与直接连接 2 2 设置灌铜安全间距 xff08 Clearance xff09 2 3 设置空心灌铜 2 4 调整灌铜形状 2 5
  • 基于MFC的USB上位机开发(3)数据传输模块

    延伸阅读 基于MFC的USB上位机开发 1 概述 基于MFC的USB上位机开发 2 速度测试模块 基于MFC的USB上位机开发 3 数据传输模块 基于MFC的USB上位机开发 4 环路模块 基于MFC的USB上位机开发 5 下环路模块 目录
  • 虚拟现实的起源、发展、爆发与沉淀

    虚拟现实的三生三世 闲来无事翻篇外文 xff0c 本博主并非故意蹭热点 xff08 奸笑 xff09 xff0c 结尾我会细说为何是三生三世 xff0c 不是五生五世 xff1a 虚拟现实远早于这个概念被创造和形式化之前 在这篇描写虚拟现实
  • 基于卷积的密度统计(一)密度图的生成

    在基于卷积的人数统计中 xff0c 一种是最后回归整张图的人数 xff0c 即输入图像 xff0c 输出人群个数 xff0c 另外一种是回归人群分布密度图 xff0c 即输入图像 xff0c 得到的是密度分布 显然得到密度分布的人数统计方法
  • Python使用国内镜像

    国内镜像地址 xff1a 阿里云 xff1a https mirrors aliyun com pypi simple 清华 xff1a https pypi tuna tsinghua edu cn simple中国科技大学 https
  • [RK3568 Android11] 开发之蓝牙(AP6275S)

    目录 前言 一 设备树dts配置 二 蓝牙打不开 三 蓝牙打开成功 四 修改蓝牙设备名称
  • XFCE菜单列表

    vim etc xdg xfce4 panel xfce4 menu 19 rc use default menu 61 true menu file 61 etc xdg menus xfce applications menu icon
  • X的DISPLAY=:0.0

    ZZ http blog 163 com caizf1987 64 126 blog static 13212128020104611592660 在Linux Unix类操作系统上 DISPLAY用来设置将图形显示到何处 直接登陆图形界面
  • 国外服务器上玩游戏延迟很高,什么原因造成的?

    在国外服务器玩游戏为什么延迟很高 有的比较热门的国际游戏 xff0c 为了玩家通常都会将整个游戏的区服划分为亚服 欧服 美服 东南亚服 韩服等等 xff0c 这主要是为了玩家有个良好的游戏体验 xff0c 那为什么在外服 国外服务器 上玩游
  • 公司抽奖小程序(自定义名单,空格控制滚动、抽奖,可作弊,可满足千人团队, 带可执行程序下载及源代码)

    内含 抽奖小程序 及 名单生成工具 使用时将两个小程序放在 同一目录下 先用名单生成工具生成名单 打开工具 按照提示输入要创建的参与抽奖的人数 输入每个人的编号及姓名 每行一个编号 空格 姓名 打开程序 复制粘贴即可 先用excel或者tx
  • 大厂面试之JAVA核心技能:Slipped Conditions

    聊聊Splipped Condtion 定义一个更现实的例子解决Slipped Conditions问题 定义 所谓Slipped conditions xff0c 就是说 xff0c 从一个线程检查某一特定条件到该线程操作此条件期间 xf