临界区, 竞态条件

临界区是程序中使用临界资源的一段程序

在同步的程序设计中,临界区 (Critical section) 指的是一个访问共享资源 (例如: 共享设备或是共享存储器) 的程序片段,而这些共享资源又无法同时被多个线程访问的特性。

当有线程进入临界区段时,其他线程或是行程必须等待 (例如: bounded waiting 等待法) ,有一些同步的机制必须在临界区段的进入点与离开点实现,以确保这些共享资源是被异或的使用,例如: semaphore。

只能被单一线程访问的设备,例如: 打印机。

临界区 Critical section

保证在某一时刻只有一个线程能访问数据的简便方法,在任意时刻只允许一个线程对资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后,其他所有试图访问临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的.

临界区指的是一个访问共用资源 (例如: 共用设备或是共用存储器) 的程序片段,而这些共用资源又无法同时被多个线程访问的特性。当有线程进入临界区段时,

其他线程或是进程必须等待 (例如: bounded waiting 等待法) ,有一些同步的机制必须在临界区段的进入点与离开点实现,以确保这些共用资源是被互斥获得使用,

竞态条件与临界区

在同一程序中运行多个线程本身不会导致问题,问题在于多个线程访问了相同的资源。如,同一内存区 (变量,数组,或对象) 、系统 (数据库,web services 等) 或文件。实际上,这些问题只有在一或多个线程向这些资源做了写操作时才有可能发生,只要资源没有发生变化,多个线程读取相同的资源就是安全的。

多线程同时执行下面的代码可能会出错:

public class Counter {

protected long count = 0;

public void add(long value){

this.count = this.count + value;

}

}

想象下线程 A 和 B 同时执行同一个 Counter 对象的 add()方法,我们无法知道操作系统何时会在两个线程之间切换。JVM 并不是将这段代码视为单条指令来执行的,而是按照下面的顺序:

从内存获取 this.count 的值放到寄存器

将寄存器中的值增加 value

将寄存器中的值写回内存

观察线程 A 和 B 交错执行会发生什么:

this.count = 0;

A: 读取 this.count 到一个寄存器 (0)

B: 读取 this.count 到一个寄存器 (0)

B: 将寄存器的值加 2

B: 回写寄存器值(2)到内存. this.count 现在等于 2

A: 将寄存器的值加 3

A: 回写寄存器值(3)到内存. this.count 现在等于 3

两个线程分别加了 2 和 3 到 count 变量上,两个线程执行结束后 count 变量的值应该等于 5。然而由于两个线程是交叉执行的,两个线程从内存中读出的初始值都是 0。然后各自加了 2 和 3,并分别写回内存。最终的值并不是期望的 5,而是最后写回内存的那个线程的值,上面例子中最后写回内存的是线程 A,但实际中也可能是线程 B。如果没有采用合适的同步机制,线程间的交叉执行情况就无法预料。

竞态条件 & 临界区

当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞态条件发生的代码区称作临界区。上例中 add()方法就是一个临界区,它会产生竞态条件。在临界区中使用适当的同步就可以避免竞态条件。

http://wiki.jikexueyuan.com/project/java-concurrent/race-conditions-and-critical-sections.html