Compare and Swap 、Anatomy of a Synchronizer

一、Compare and Swap

Compare and swap is a technique used when designing concurrent algorithms. Basically, compare and swap compares an expected value to the concrete value of a variable, and if the concrete value of the variable is equals to the expected value, swaps the value of the variable for a new variable. Compare and swap may sound a bit complicated but it is actually reasonably simple once you understand it, so let me elaborate a bit further on the topic.
Compare and swap是设计并发算法时使用的一种技术。 Compare and swap将期望值与变量的实际值进行比较:如果变量的具体值等于期望值,则将变量的值调换为新变量。

What Situations Compare And Swap is Intended to Support

A very commonly occurring patterns in programs and concurrent algorithms is the “check then act” pattern. The check then act pattern occurs when the code first checks the value of a variable and then acts based on that value. Here is a simple example:
在程序和并发算法中,“先检查后行动”模式很长常见。 代码首先检查变量的值,然后根据该值进行操作时,将会出现以上模式。 这是一个简单的示例:

class MyLock {

    private boolean locked = false;

    public boolean lock() {
        if(!locked) {
            locked = true;
            return true;
        }
        return false;
    }
}

This code has many errors if it was to be used in a multithreaded application, but please ignore that for now.
如果要在多线程应用程序中使用此代码,则有很多错误,但是现在请忽略它。

As you can see, the lock() method first checks if the locked member variable is equal to false (check), and if it is it ses locked to true (then act).
如您所见,lock()方法首先检查成员变量locked是否等于false,若是false,则将其值改为true。

If multiple threads had access to the same MyLock instance, the above lock() function would not be guaranteed to work. If a thread A checks the value of locked and sees that it is false, a thread B may also check the value of locked at exactly the same time and see it as false. Or, in fact thread B may check locked at any time between thread A has checked locked and seen it to be false, and before thread A sets locked to true. Thus, both thread A and thread B may see locked as being false, and both will then act based on that information.
如果多个线程可以访问同一个MyLock实例,则不能保证上述lock()函数可以正常工作:
线程A检查locked的值为false
同时(或在线程A修改值之前),线程B检查locked值为false
然后,线程A和线程B,都可以将locked改为true

To work properly in a multithreaded application, “check then act” operations must be atomic. By atomic is meant that both the “check” and “act” actions are executed as an atomic (non-dividable) block of code. Any thread that starts executing the block will finish executing the block without interference from other threads. No other threads can execute the atomic block at the same time.
为了在多线程应用程序中正常工作,“先检查后行动”操作必须是原子操作。 “原子”是指“检查”和“动作”作为一个原子(不可分割)操作。任何开始执行该块的线程都将完成该块的执行,而不会受到其他线程的干扰。没有其他线程可以同时执行原子块。

Here is the code example from earlier with the lock() method turned into an atomic block of code using the synchronized keyword:
这是先前的代码示例,其中使用synchronized 关键字将lock()方法变成了一个原子代码块:

class MyLock {

    private boolean locked = false;

    public synchronized boolean lock() {
        if(!locked) {
            locked = true;
            return true;
        }
        return false;
    }
}

Now the lock() method is synchronized so only one thread can executed it at a time on the same MyLock instance. The lock() method is effectively atomic.
现在,lock()方法已同步,因此同一时刻,只能MyLock实例的一个线程执行。 lock()方法实际上是原子的。

The atomic lock() method is actually an example of “compare and swap”. The lock() method compares the variable locked to the expected value false and if locked is equal to this expected value, it swaps the variable’s value to true .
原子lock()方法实际上是“compare and swap”的一个示例。 lock()方法将比较locked变量与期望值false进行比较,如果被锁定等于该期望值,则将变量的值调换为true。

Compare And Swap As Atomic Operation

Modern CPUs have built-in support for atomic compare and swap operations. From Java 5 you can get access to these functions in the CPU via some of the new atomic classes in the java.util.concurrent.atomic package.
现代CPU对原子"compare and swap"操作做了内置支持。 从Java 5中,您可以通过java.util.concurrent.atomic包中的一些新的原子类访问CPU中的这些功能。

Here is an example showing how to implement the lock() method shown earlier using the AtomicBoolean class:
这是一个示例,显示了如何使用AtomicBoolean类实现前面显示的lock()方法:

public static class MyLock {
    private AtomicBoolean locked = new AtomicBoolean(false);

    public boolean lock() {
        return locked.compareAndSet(false, true);
    }

}

Notice how the locked variable is no longer a boolean but an AtomicBoolean. This class has a compareAndSet() function which will compare the value of the AtomicBoolean instance to an expected value, and if has the expected value, it swaps the value with a new value. In this case it compares the value of locked to false and if it is false it sets the new value of the AtomicBoolean to true.
请注意,锁定变量不再是boolean而是AtomicBoolean。 此类具有compareAndSet()函数,该函数会将AtomicBoolean实例的值与期望值进行比较,如果实际值与期待值相同,则调换成新值,并返回true。如果不一致,则返回false。
在这里插入图片描述
这里的成功指实际值与期待值一致。

The advantage of using the compare and swap features that comes with Java 5+ rather than implementing your own is that the compare and swap features built into Java 5+ lets you utilize the underlying compare and swap features of the CPU your application is running on. This makes your compare and swap code faster.
使用Java 5+内置的“compare and swap”而不使用自己实现的优点是:Java 5+可以让你在CPU的基础使用,使代码运行速度更快。

二、Anatomy of a Synchronizer

Even if many synchronizers (locks, semaphores, blocking queue etc.) are different in function, they are often not that different in their internal design. In other words, they consist of the same (or similar) basic parts internally. Knowing these basic parts can be a great help when designing synchronizers. It is these parts this text looks closer at.
即使许多同步器(锁,信号量,阻塞队列等)在功能上有所不同,它们的内部设计通常也没有太大不同。换句话说,它们在内部由相同(或相似)的基本部分组成。在设计同步器时,了解这些基本部分会很有帮助。本文正是这些部分的重点。

The purpose of most (if not all) synchronizers is to guard some area of the code (critical section) from concurrent access by threads. To do this the following parts are often needed in a synchronizer:
大多数(如果不是全部)同步器的目的是保护代码的某些区域(临界区)免受线程的并发访问。为此,同步器中通常需要以下部分:

  1. State
  2. Access Condition
  3. State Changes
  4. Notification Strategy
  5. Test and Set Method
  6. Set Method

Not all synchronizers have all of these parts, and those that have may not have them exactly as they are described here. Usually you can find one or more of these parts, though.
并非所有同步器都具有所有这些部分,而那些同步器可能并不完全具有此处所述的内容。 但是,通常您可以找到这些部分中的一个或多个。

State

The state of a synchronizer is used by the access condition to determine if a thread can be granted access. In a Lock the state is kept in a boolean saying whether the Lock is locked or not. In a Bounded Semaphore the internal state is kept in a counter (int) and an upper bound (int) which state the current number of “takes” and the maximum number of “takes”. In a Blocking Queue the state is kept in the List of elements in the queue and the maximum queue size (int) member (if any).
用同步器的状态来确定是否可以给线程授权。
Lock中,状态是boolean值,来判断Lock是否锁住。
Bounded Semaphore中,状态保存在一个计数器(int)和一个上限(int)中,该状态指示当前的“ takes”数量和“ takes”的最大数量。
在“Blocking Queue”中,状态保存在队列中的元素列表、队列最大长度值(如果有)中。

Here are two code snippets from both Lock and a BoundedSemaphore. The state code is marked in bold.
这是Lock和BoundedSemaphore的两个代码段。

public class Lock{

  //state is kept here
// -------------------
  private boolean isLocked = false; 
// --------------------
  public synchronized void lock()
  throws InterruptedException{
    while(isLocked){
      wait();
    }
    isLocked = true;
  }

  ...
}
public class BoundedSemaphore {

  //state is kept here
  // -------------------
      private int signals = 0;
      private int bound   = 0;
  // -------------------  
  public BoundedSemaphore(int upperBound){
    this.bound = upperBound;
  }

  public synchronized void take() throws InterruptedException{
    while(this.signals == bound) wait();
    this.signal++;
    this.notify();
  }
  ...
}

Access Condition

The access conditions is what determines if a thread calling a test-and-set-state method can be allowed to set the state or not. The access condition is typically based on the state of the synchronizer. The access condition is typically checked in a while loop to guard against Spurious Wakeups. When the access condition is evaluated it is either true or false.
访问条件决定了是否可以允许调用test-and-set-state方法的线程设置状态。 访问条件通常基于同步器的状态。 通常在while循环中检查访问条件,以防止Spurious Wakeups。 评估访问条件时,它是true还是false。

In a Lock the access condition simply checks the value of the isLocked member variable. In a Bounded Semaphore there are actually two access conditions depending on whether you are trying to “take” or “release” the semaphore. If a thread tries to take the semaphore the signals variable is checked against the upper bound. If a thread tries to release the semaphore the signals variable is checked against 0.
在Lock中,访问条件只是检查isLocked成员变量的值。
在Bounded Semaphore中,有两种访问条件,具体取决于方法是take()还是release():如果为take(),则将信号与上限比较;如果为release(),则信号与0进行比较。

Here are two code snippets of a Lock and a BoundedSemaphore with the access condition marked in bold. Notice how the conditions is always checked inside a while loop.
这是Lock和BoundedSemaphore的两个代码段。条件检查总是在while里面。

public class Lock{

  private boolean isLocked = false;

  public synchronized void lock()
  throws InterruptedException{
    //access condition while(isLocked)
     while(isLocked){
      wait();
    }
    isLocked = true;
  }

  ...
}
public class BoundedSemaphore {
  private int signals = 0;
  private int bound   = 0;

  public BoundedSemaphore(int upperBound){
    this.bound = upperBound;
  }

  public synchronized void take() throws InterruptedException{
    //access condition  while(this.signals == bound)
    while(this.signals == bound) wait();
    this.signals++;
    this.notify();
  }

  public synchronized void release() throws InterruptedException{
    //access condition while(this.signals == 0)
    while(this.signals == 0) wait();
    this.signals--;
    this.notify();
  }
}

State Changes

Once a thread gains access to the critical section it has to change the state of the synchronizer to (possibly) block other threads from entering it. In other words, the state needs to reflect the fact that a thread is now executing inside the critical section. This should affect the access conditions of other threads attempting to gain access.
一旦线程获得对临界区的访问权,它就必须更改状态,以阻止其他线程进入临界区。 换句话说,状态需要反映一个事实,即线程正在临界区执行,这会影响到那些尝试获得权限的线程。

In a Lock the state change is the code setting isLocked = true. In a semaphore it is either the code signals-- or signals++;
在“lock”中,在临界区isLocked = true。
在信号量中,是signals-- 或者signals++。

public class Lock{

  private boolean isLocked = false;

  public synchronized void lock()
  throws InterruptedException{
    while(isLocked){
      wait();
    }
    //state change
    isLocked = true;
  }

  public synchronized void unlock(){
    //state change
    isLocked = false;
    notify();
  }
}
public class BoundedSemaphore {
  private int signals = 0;
  private int bound   = 0;

  public BoundedSemaphore(int upperBound){
    this.bound = upperBound;
  }

  public synchronized void take() throws InterruptedException{
    while(this.signals == bound) wait();
    //state change
    this.signals++;
    this.notify();
  }

  public synchronized void release() throws InterruptedException{
    while(this.signals == 0) wait();
    //state change
    this.signals--;
    this.notify();
  }
}

Notification Strategy

Once a thread has changed the state of a synchronizer it may sometimes need to notify other waiting threads about the state change. Perhaps this state change might turn the access condition true for other threads.
一旦线程更改了同步器的状态,有时可能需要将状态更改通知其他正在等待的线程。也许此状态更改可能会使其他线程的访问条件变为true。

Notification Strategies typically fall into three categories.
通知策略通常分为三类:

  1. Notify all waiting threads.
  2. Notify 1 random of N waiting threads.
  3. Notify 1 specific of N waiting thread.

Notifying all waiting threads is pretty easy. All waiting threads call wait() on the same object. Once a thread want to notify the waiting threads it calls notifyAll() on the object the waiting threads called wait() on.
通知所有正在等待的线程非常容易。所有等待线程都在同一对象上调用wait()。一旦线程想要通知等待的线程,它将在对象上调用notifyAll()。

Notifying a single random waiting thread is also pretty easy. Just have the notifying thread call notify() on the object the waiting threads have called wait() on. Calling notify makes no guarantee about which of the waiting threads will be notified. Hence the term “random waiting thread”.
通知一个随机的等待线程也很容易。只是调用那个对象的notify()即可。调用notify不能保证将通知哪个等待线程。因此,术语“随机等待线程”。

Sometimes you may need to notify a specific rather than a random waiting thread. For instance if you need to guarantee that waiting threads are notified in a specific order, be it the order they called the synchronizer in, or some prioritized order. To achive this each waiting thread must call wait() on its own, separate object. When the notifying thread wants to notify a specific waiting thread it will call notify() on the object this specific thread has called wait() on. An example of this can be found in the text Starvation and Fairness。
有时您可能需要通知特定的线程而不是随机的等待线程。例如,如果您需要确保以特定顺序通知正在等待的线程,则可以是它们调用同步器的顺序,也可以是某些优先顺序。为了达到这个目的,每个等待线程必须在其自己的单独对象上调用wait()。当通知线程想要通知特定的等待线程时,它将在该特定线程已调用wait()的对象上调用notify()。比如“Starvation and Fairness”中的A Fair Lock节,将等待线程放在队列里,每次只唤醒队首线程。

Below is a code snippet with the notification strategy (notify 1 random waiting thread) marked in bold:

public class Lock{

  private boolean isLocked = false;

  public synchronized void lock()
  throws InterruptedException{
    while(isLocked){
      //wait strategy - related to notification strategy
      wait();
    }
    isLocked = true;
  }

  public synchronized void unlock(){
    isLocked = false;
    notify(); //notification strategy
  }
}

Test and Set Method

Synchronizer most often have two types of methods of which test-and-set is the first type (set is the other). Test-and-set means that the thread (calling this method) tests the internal state of the synchronizer against the access condition. If the condition is met the thread sets the internal state of the synchronizer to reflect that the thread has gained access.
同步器通常有两种类型的方法,其中测试设置是第一种类型(另一种是设置)。
Test-and-set
测试并设置意味着调用此方法的线程针对访问条件测试同步器的内部状态。如果满足条件,则线程将设置同步器的内部状态以反映该线程已获得访问权限。

The state transition usually results in the access condition turning false for other threads trying to gain access, but may not always do so. For instance, in a Read - Write Lock a thread gaining read access will update the state of the read-write lock to reflect this, but other threads requesting read access will also be granted access as long as no threads has requested write access.

It is imperative that the test-and-set operations are executed atomically meaning no other threads are allowed to execute in the test-and-set method in between the test and the setting of the state.

The program flow of a test-and-set method is usually something along the lines of:

状态转换通常会导致其他线程尝试获取访问的访问条件变为假,但可能并非总是如此。例如,在“读写锁定”中,获得读取访问权限的线程将更新读写锁定的状态以反映该状态,但是只要没有线程请求写入访问,则其他请求读取访问的线程也将被授予访问权限。

必须以原子方式执行测试设置操作,这意味着在测试和状态设置之间不允许在测试设置方法中执行其他线程。

测试设置方法的程序流程通常类似于以下内容:
Set state before test if necessary
Test state against access condition
If access condition is not met, wait
If access condition is met, set state, and notify waiting threads if necessary
The lockWrite() method of a ReadWriteLock class shown below is an example of a test-and-set method. Threads calling lockWrite() first sets the state before the test (writeRequests++). Then it tests the internal state against the access condition in the canGrantWriteAccess() method. If the test succeeds the internal state is set again before the method is exited. Notice that this method does not notify waiting threads.

public class ReadWriteLock{
    private Map<Thread, Integer> readingThreads =
        new HashMap<Thread, Integer>();

    private int writeAccesses    = 0;
    private int writeRequests    = 0;
    private Thread writingThread = null;

    ...

    
        public synchronized void lockWrite() throws InterruptedException{
        writeRequests++;
        Thread callingThread = Thread.currentThread();
        while(! canGrantWriteAccess(callingThread)){
        wait();
        }
        writeRequests--;
        writeAccesses++;
        writingThread = callingThread;
        }
    

    ...
}

The BoundedSemaphore class shown below has two test-and-set methods: take() and
release(). Both methods test and sets the internal state.

public class BoundedSemaphore {
  private int signals = 0;
  private int bound   = 0;

  public BoundedSemaphore(int upperBound){
    this.bound = upperBound;
  }

  
      public synchronized void take() throws InterruptedException{
      while(this.signals == bound) wait();
      this.signals++;
      this.notify();
      }

      public synchronized void release() throws InterruptedException{
      while(this.signals == 0) wait();
      this.signals--;
      this.notify();
      }
  
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值