java race condition_Java中的Race condition和Critical section(译)

Java中的Race condition和Critical section(译)

race condition,即竞态,是一种可能发生于critical section中的特殊状态,critical section一般翻译为临界区,我个人认为临界区不是很直观,因此不做翻译,仍然用英文。这里的critical section实际上是一个可能会被多个线程并发执行的代码区段,并发线程的不同执行顺序会直接影响整个并发程序的执行结果。

由于并发线程的不同执行顺序直接影响这部分代码的执行结果,这时我们就说这部分代码为critical section,而critical section的代码被多个线程并发执行的时候,则处于被竞争执行的状态,即race condition。

Critical section

事实上,同一段代码在多个线程并发执行时并不一定会引发问题,而是在多个线程同时竞争访问共享的资源时才有可能引发race condition。这里的资源包括内存资源(变量,数组或对象等),系统资源(数据库,web服务等)以及文件等。

实际上只有多个线程并发对共享的资源进行写操作时才有可能引发问题。多个线程读共享资源并不会引发什么问题。

下面的这段代码是一个critical section例子,多线程并发执行时会引发问题:

假设两个不同的线程A和B,通过一个相同的Counter对象,并发执行该对象的add方法。操作系统何时进行线程的切换是不确定的。add方法内部的这行代码编译成字节码在JVM内部执行时并不是一个原子操作,而是分解成了类似如下所示的几个字令来执行:

1、 从主内存将this.count读到CPU的寄存器内;

2、 将value值加到寄存器的值;

3、 将寄存器内的值写回到主内存。

将这个过程,对应到两个两个线程A、B并发执行,则可能以如下的顺序进行执行:

本来这两个线程A,B是想将2和3加到count上,预期的结果应该是5才对。但是由于这两个线程是并发交叉执行的,导致线程A的计算结果3最终写回到主内存,同时也覆盖了现场B写会到主内存的2。执行结果不符合预期。当然,这里假象出来的这个执行流程只是一种可能发生的情况,也有可能执行结果是2或5。但是只要有这种竞态发生的可能性,这种代码段就是critical section,是我们需要极力杜绝的。

如何避免race condition

那么我们如何避免race condition的发生呢?答案是原子性。

我们需要将有可能发生竞态的critical section包成一个原子操作,即如果一个线程正在执行这部分代码,那么其他线程只能等到该线程结束执行离开后才能开始执行。

具体来说,我们可以通过一些线程之间相互同步的手段来实现。比如:

1、 synchronized代码块;

2、 锁;

3、 原子性变量,如java.util.concurrent.atomic.AtomicInteger。

critical section的吞吐量

对于逻辑简单的critical section,通过synchronized代码块来避免竞态没啥问题,但是对于逻辑复杂、代码量大的critical section来说,这种方式无疑会降低整个系统的吞吐量。这时我们可以尝试将其拆解成多个独立的、较小的critical section。

举个例子:

这里我们为了避免竞态的放生,用synchronized将代码段包了下。这样在多线程并发执行时,这些线程只能挨个轮流执行该代码段。但是我们细想就会发现,这个代码段其实可以拆分成两个独立的、互不影响的子代码段:

这样如果两个线程并发执行add方法,则可以再一个线程执行第一个子代码段的同时另一个线程执行第二个代码段,因此避免了更长时间的相互等待,提升了吞吐量。

当然,这个例子非常简单,仅为了说明原理,实际项目中可能需要更加认真的分析才知道如何进行拆分。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值