文档章节

【06】竞态条件与临界区

秋雨霏霏
 秋雨霏霏
发布于 2017/07/22 18:31
字数 1250
阅读 40
收藏 0

竞态条件 是一个可能在临界区中发生的特殊条件。 而临界区表示着,一段在并发执行和顺序执行时有着不同运行结果的代码片段。

临界区中多线程执行的结果可能会因不同的线程执行顺序,而导致不同的运行结果。 竞态条件就是源自线程同时竞争执行临界区,而竞争的结果也会影响着临界区的运行结果。

接下来再详细说说这两个概念。

临界区

在一个应用中运行多个线程,这本身不会有啥问题。 问题是出在,多个线程访问同一资源的时候。 例如:同一个内存区域(变量,数组,对象),系统资源(数据库,接口)以及文件等。

事实上,问题主要是线程对资源进行写操操作。 如果,多个线程都是执行资源读操作,那是不会有什么问题的。

下面这个例子就展示了一个临界区,当多个线程同时执行时,就会出问题:

public class Counter {

 protected long count = 0;

 public void add(long value){
     this.count = this.count + value;
 }
}

想象一下,如果有两个线程,A和B,在同一个实例上进行加法操作。 而我们无法知道操作是如何调度这两个线程的执行。 虽然只有一行代码,但在JVM中add()方法不会以原子的方式被执行。 而是会以下面的逻辑来处理:

  1. 从内存读取this.count的值到寄存器中
  2. 在寄存中执行加法指令操作
  3. 将计算结果从寄存器中写会内存

而当A,B两个线程交叉执行时,可能会得到如下的情况:

    this.count = 0;

A: 读取this.count到寄存器           (0)
B: 读取this.count到寄存器           (0)
B: 在寄存器中加上2                  
B: 将结果2写回内存。this.count=2    (2)
A: 在寄存器中加上3
A: 将结果3写回内存。this.count=3    (3)

两个线程分别想要对计数器累加2和3。所以,执行完成时预期值应该是5。 但是,两个线程的执行过程是交叉进行的,这样就会导致结果不同了。

如果按上面的顺序执行,最后A线程的结果会直接覆盖B线程的结果。

临界区中的竞态条件

上例中,add()方法就是一个临界区。当多个线程执行时,触发竞态条件。

就是说,两个线程争用同一个资源,此时,执行顺序就变得很敏感了,这也就称之为竞态条件。 导致竞态条件的代码块就称为临界区。

防止竞态条件

要想防止竞态条件的发生,就要确保临界区以原子方式执行。 这就意味着,一次只有一个线程在执行,在第一个线程离开临界区之前,不会有其他线程可以进入临界区。

为了避免竞态条件,可以对临界区使用一些同步机制。线程同步可以使用Java阻塞同步,还可以使用Lock以及原子变量来进行同步。

临界区吞吐量

在上例中,对整个临界区进行同步阻塞是可以解决问题的。一般来说,对于临界区是越小越好,这样可以让每一个线程执行更小的临界区。 为了减少对共享资源的争用,这样就可以增加总体上的吞吐量。

来看看这个例子:

public class TwoSums {
    
    private int sum1 = 0;
    private int sum2 = 0;
    
    public void add(int val1, int val2){
        synchronized(this){
            this.sum1 += val1;   
            this.sum2 += val2;
        }
    }
}

注意add()方法,对两个成员变量分别进行累加和。 为了防止累加和出现竞态条件,这里使用了一个Java synchronized 阻塞。 这样一次只会有一个线程能够进行累加和的计算。

但是,要注意,两个变量的累加计算,其实是互相独立的,其实是可以分成两个同步块:

public class TwoSums {
    
    private int sum1 = 0;
    private int sum2 = 0;

    private Integer sum1Lock = new Integer(1);
    private Integer sum2Lock = new Integer(2);

    public void add(int val1, int val2){
        synchronized(this.sum1Lock){
            this.sum1 += val1;   
        }
        synchronized(this.sum2Lock){
            this.sum2 += val2;
        }
    }
}

这样两个线程就可以并行进入add()方法。 一个进入第一个同步块,另一个进入第二个。 两个同步块分别在两个不同的对象上进行同步,所以两个不同的线程可以互不干扰的进入两个代码块中。 这样在add()方法中,就可以减少线程等待的时间。

当然,这个例子太简单了。在实际工作中,将共享资源分解到多个临界区,其实是很复杂的工作。 需要分析更多情况下的执行顺序。

© 著作权归作者所有

共有 人打赏支持
秋雨霏霏
粉丝 148
博文 91
码字总数 160620
作品 0
杭州
CTO(技术副总裁)
【Java并发性和多线程】竞态条件与临界区

本文为转载学习 原文链接:http://tutorials.jenkov.com/java-concurrency/race-conditions-and-critical-sections.html 译文链接:http://ifeve.com/race-conditions-and-critical-section......

heroShane
2014/01/28
0
0
并发编程QA

1、什么时候应该使用多线程 1⃣️ 多CPU的情况 2⃣️ IO等待 如果有一个线程在执行的时候,遇到了磁盘读写或者网络传输阻塞,那么线程就需要等待,这时候占用的CPU可以释放,然后CPU就可以将...

whc20011
2016/10/18
10
0
Java并发库(Java Concurrency)

原文地址 译文地址 译者:张坤等 Java并发性和多线程介绍(Java Concurrency / Multithreading Tutorial) 多线程的优点(Multithreading Benefits) 多线程的代价(Multithreading Costs) ...

暗之幻影
2016/12/17
19
0
[shell应用进阶]:限制同时运行脚本实例的个数 -- 串行化:换一个思路。

【背景介绍】 CU上曾经有几个帖子讨论到一个实际问题,就是如何限制同一时刻只允许一个脚本实例运行。其中本版新老斑竹和其它网友都参加了讨论,但以faintblue兄的帖子对大家启发最大,下面的...

红薯
2009/05/06
664
0
linux设备驱动系列:如何处理竞态关系

综述 在上一篇介绍了linux驱动的调试方法,这一篇介绍一下在驱动编程中会遇到的并发和竟态以及如何处理并发和竞争。 首先什么是并发与竟态呢?并发(concurrency)指的是多个执行单元同时、并行...

东辉在线
2015/04/11
0
0

没有更多内容

加载失败,请刷新页面

加载更多

docker-compose ,docker-stack

1.例子 version: "3"services: php: image: registry.cn-hangzhou.aliyuncs.com/lxepoo/apache-php5 ports: - "38080:80" networks: - my_php_mysql volum......

chenbaojun
31分钟前
1
0
SQL_Server2000示例数据库NorthWind的分析(转)

SQL_Server2000示例数据库NorthWind的分析 表名:Categories(食品类别表) 表结构: 字段名称 数据类型 长度 允许为空 CategoryID(主键) int 4 否 CategoryName nvarchar 15 否 Description ...

QQZZFT
34分钟前
1
0
laravel 5.5 Session store not set on request.

laravel 5.5 数据存入session,会出现Session store not set on request.错误。查了下laravel 5.5将session放到global middleware中,需要laravel的文件 ./app/Http/Kernel.php中的加上一句:...

MichaelShu
今天
1
0
OpenCV VideoCapture.get()参数详解

param define cv2.VideoCapture.get(0) 视频文件的当前位置(播放)以毫秒为单位 cv2.VideoCapture.get(1) 基于以0开始的被捕获或解码的帧索引 cv2.VideoCapture.get(2) 视频文件的相对位置(...

NateHuang
今天
0
0
java基础知识,小栗子

来操作一下数组.....注意带参数的变长数组的使用. package com.avatus;import java.util.Random;import java.util.Scanner;public class Main { public static void main(St...

Oh_really
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部