原子性,可见性,有序性

463 阅读2分钟

注:此文主要是参考1文章的摘录,方便自己复习。推荐直接阅读参考1

编写并发程序的错误主要来自3个方面的问题,原子性,可见性,有序性

原子性

Atomicity deals with which actions and sets of actions have indivisible effects. This is the aspect of concurrency most familiar to programmers: it is usually thought of in terms of mutual exclusion. Visibility determines when the effects of one thread can be seen by another. Ordering determines when actions in one thread can be seen to occur out of order with respect to another.

Definition

If an action is (or a set of actions are) atomic, its result must be seen to happen ``all at once'', or indivisibly. Atomicity is the traditional bugbear of concurrent programming.

Example 1"

class BrokenBankAccount {
  private int balance;

  synchronized int getBalance() {
    return balance;
  }

  synchronized void setBalance(int x) 
    throws IllegalStateException {
    balance = x;
    if (balance < 0) {
      throw new IllegalStateException("Negative Balance");
    }
  }

  void deposit(int x) {
    int b = getBalance();
    setBalance(b + x);
  }

  void withdraw(int x) {
    int b = getBalance();
    setBalance(b - x);
  }
}

共享变量的访问通过加锁同步了,因此无数据竞争问题。然而存在原子性问题。如deposit()与withdraw()被俩个线程执行。

可见性

可见性描述的是一个线程的写能否被另一个线程的读可见(观察到)。我们可以通过同步的方式来保障可见性。同步包含锁,volatile, final, 类的初始化,线程的start & join 等方式

Example 2

class LoopMayNeverEnd { 
  boolean done = false; 

  void work() { 
    while (!done) { 
      // do work 
    } 
  } 
 
  void stopWork() { 
    done = true; 
  } 
} 

实践中,因为编译器注意到第一个线程没有写入done。因此只会读取done一次。把循环改成了无穷循环(while(true))。从而导致线程1看不到线程2的写入。 我们把done定义为volatile变量,那么编译器将无法阻止线程1看到线程2的写入。

有序性

Example 3

class BadlyOrdered {
  boolean a = false;
  boolean b = false;

  void threadOne() {
    a = true;
    b = true;
  }

  boolean threadTwo() {
    boolean r1 = b; // sees true
    boolean r2 = a; // sees false
    boolean r3 = a; // sees true
    return (r1 && !r2) && r3; // returns true
  }

}

这段程序可能返回true,意味着线程2先看到对b的写入为true,然后r2读取到a的默认值false,然后r3读取到a的写入为true。显然,线程1的俩个语句被重排序了。另外这里由于线程1,2之间没有做同步,存在数据竞争。根据JMM线程2的对a,b的读取极可能读取到默认值,也可能读取到线程1的写入。

Ref

  1. jeremymanson.blogspot.com/2007/?m=0
  2. \