Concurrency in action

什么是竞态条件?


tips: The situation where two threads compete for the same resource, where the sequence in which the resource is accessed is significant, is called race conditions. A code section that leads to race conditions is called a critical section. In the below example the method add() is a critical section, leading to race conditions. Race conditions can be avoided by proper thread synchronization in critical sections.
两个线程竞争同一个资源,而该资源的访问顺序十分重要的情况就被称为竞态条件。导致竞态条件的代码区就被称为临界区。下面的例子中的add()方法就是一个导致竞态条件的临界区。临界区上合适的线程同步能避免竞态条件。
Race conditions arise in software when an application depends on the sequence or timing of processes or threads for it to operate properly.
race conditions often happen when the processes or threads depend on some shared state.
软件里的竞态条件发生在一个应用程序依赖进程或者线程的执行顺序和时间以确保该程序执行正确的时候。竞态条件总是发生在进程或线程依赖某些共享资源的时候。

一个临界区的例子:

1
2
3
4
5
6
7
public class Counter {
protected long count = 0;

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

什么是线程安全?


tips: Code that is safe to call by multiple threads simultanously is called thread safe. If a piece of code is thread safe, then it contains no race conditions.
能被多个线程同时安全地调用的代码就是线程安全。如果一段代码是安全的,那么它就不存在竞态条件。

  • Thread safe: Implementation is guaranteed to be free of race conditions when accessed by multiple threads simultaneously.
  • Conditionally safe: Different threads can access different objects simultaneously, and access to shared data is protected from race conditions.
  • Not thread safe: Code should not be accessed simultaneously by different threads.
  • 线程安全:实现以保证多个线程同时访问不存在竞态条件
  • 条件安全:不同的线程可以同时访问不同的对象,且在访问共享资源时保护以免于竞态条件
  • 非线程安全:代码不能被多个线程同时访问

可见性和竞态条件的关系?


如果存在竞态条件,那么就必须保证变量的可见性

可见性和线程安全的关系?


严格意义上,线程安全其实是相对于非线程安全的,即包含了线程安全和条件安全两部分。

Below we discuss two approaches for avoiding race conditions to achieve thread safety.

The first class of approaches focuses on avoiding shared state, and includes:
也就是纯粹的线程安全。

  • Re-entrancy
    Writing code in such a way that it can be partially executed by a thread, reexecuted by the same thread or simultaneously executed by another thread and still correctly complete the original execution. This requires the saving of state information in variables local to each execution, usually on a stack, instead of in static or global variables or other non-local state. All non-local state must be accessed through atomic operations and the data-structures must also be reentrant.
  • Thread-local storage
    Variables are localized so that each thread has its own private copy. These variables retain their values across subroutine and other code boundaries, and are thread-safe since they are local to each thread, even though the code which accesses them might be executed simultaneously by another thread.
    The second class of approaches are synchronization-related, and are used in situations where shared state cannot be avoided:
    也就是条件安全部分。
  • Mutual exclusion
    Access to shared data is serialized using mechanisms that ensure only one thread reads or writes to the shared data at any time. Incorporation of mutual exclusion needs to be well thought out, since improper usage can lead to side-effects like deadlocks, livelocks and resource starvation.
  • Atomic operations
    Shared data are accessed by using atomic operations which cannot be interrupted by other threads. This usually requires using special machine language instructions, which might be available in a runtime library. Since the operations are atomic, the shared data are always kept in a valid state, no matter how other threads access it. Atomic operations form the basis of many thread locking mechanisms, and are used to implement mutual exclusion primitives.
  • Immutable objects
    The state of an object cannot be changed after construction. This implies both that only read-only data is shared and that inherent thread safety is attained. Mutable (non-const) operations can then be implemented in such a way that they create new objects instead of modifying existing ones. This approach is used by the string implementations in Java, C# and Python.

    所以可见性只是针对于条件安全部分而言的。

竞态条件和线程安全的关系?


避免竞态条件以获得线程安全。换句话说,避免竞态条件是线程安全的充要条件。

为什么说跨越内存栅栏(可见性)和避免竞态条件是同步相关的两大主要问题?


跨越内存栅栏(可见性)是应对竞态条件的一种方式,出现竞态条件必须确保可见性。举个例子:一个写线程写完之后,要保证所做更改对其它线程可见,否则会让其它线程读到脏数据。
避免竞态条件的方式有很多种,包括上述的线程安全的各种手段。

相关术语


  1. livelock(活锁):请求一个锁的时候不断失败。
  2. starvation(饿死):无法定期访问共享资源来执行,发生在某个线程长期霸占共享资源的时候。
  • Starvation
    Starvation describes a situation where a thread is unable to gain regular access to shared resources and is unable to make progress. This happens when shared resources are made unavailable for long periods by “greedy” threads. For example, suppose an object provides a synchronized method that often takes a long time to return. If one thread invokes this method frequently, other threads that also need frequent synchronized access to the same object will often be blocked.
  • Livelock
    A thread often acts in response to the action of another thread. If the other thread’s action is also a response to the action of another thread, then livelock may result. As with deadlock, livelocked threads are unable to make further progress. However, the threads are not blocked — they are simply too busy responding to each other to resume work. This is comparable to two people attempting to pass each other in a corridor: Alphonse moves to his left to let Gaston pass, while Gaston moves to his right to let Alphonse pass. Seeing that they are still blocking each other, Alphone moves to his right, while Gaston moves to his left. They’re still blocking each other, so…