JUC AQS

AQS 是 java.util.concurrent.locks.AbstractQueuedSynchronizer 类的简称,它虽然只是一个类,但也是一个强大的框架,目的是为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)提供一个框架,这些类同步器都依赖单个原子 int 值来表示状态。

AQS 实现了控制同步的框架,并定义抽象方法留给子类定义哪种状态意味着被获取或被释放,是个典型的模板方法实现。

概述

同步器一般包含两种方法,一种是acquire,另一种是release。acquire操作阻塞调用的线程,直到或除非同步状态允许其继续执行。而release操作则是通过某种方式改变同步状态,使得一或多个被acquire阻塞的线程继续执行。

同步器的基本思想

acquire操作:

// 循环里不断尝试,典型的失败后重试
while (synchronization state does not allow acquire) {
     // 同步状态不允许获取,进入循环体,也就是失败后的处理
     enqueue current thread if not already queued;     // 如果当前线程不在等待队列里,则加入等待队列
     possibly block current thread;     // 可能的话,阻塞当前线程
}

// 执行到这里,说明已经成功获取,如果之前有加入队列,则出队列。
dequeue current thread if it was queued; 

release操作:

update synchronization state;    //  更新同步状态
if (state may permit a blocked thread to acquire) // 检查状态是否允许一个阻塞线程获取
      unblock one or more queued threads;     // 允许,则唤醒后继的一个或多个阻塞线程。

为了实现上述操作,需要下面三个基本组件的相互协作:

  • 同步状态的原子性管理:怎么判断同步器是否可用的?怎么维护原子状态不会出现非法状态?怎么让其他线程看到当前线程对状态的修改?
  • 线程的阻塞与解除阻塞:同步器不可用时,怎么挂起线程?同步器可用时,怎么恢复挂起线程继续执行?
  • 队列的管理:有多个线程被阻塞时,怎么管理这些被阻塞的线程?同步器可用时,应该恢复哪个阻塞线程继续执行?怎么处理取消获取的线程?

继续阅读

JUC 原子类

volatile 变量

volatile变量具有可见性,也就是说线程能够自动发现volatile 变量的最新值;对volatile变量进行操作不会造成阻塞。

适用于:多个变量之间或者某个变量的当前值与修改后值之间没有约束。

正确使用volatile变量的条件:

  1. 对变量的写操作不依赖于当前值。
  2. 该变量没有包含在具有其他变量的不变式中。

所以,volatile变量不支持像i++这样的原子操作,因为这条语句包含了三个步骤:读取-加1操作-写变量。
继续阅读

自旋锁、排队自旋锁、MCS锁、CLH锁

自旋锁(Spin lock)

自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。

自旋锁适用于锁保护的临界区很小的情况,临界区很小的话,锁占用的时间就很短。

简单的实现

import java.util.concurrent.atomic.AtomicReference;

public class SpinLock {
   private AtomicReference<Thread> owner = new AtomicReference<Thread>();

   public void lock() {
       Thread currentThread = Thread.currentThread();

              // 如果锁未被占用,则设置当前线程为锁的拥有者
       while (!owner.compareAndSet(null, currentThread)) {
       }
   }

   public void unlock() {
       Thread currentThread = Thread.currentThread();

              // 只有锁的拥有者才能释放锁
       owner.compareAndSet(currentThread, null);
   }
}

SimpleSpinLock里有一个owner属性持有锁当前拥有者的线程的引用,如果该引用为null,则表示锁未被占用,不为null则被占用。

这里用AtomicReference是为了使用它的原子性的compareAndSet方法(CAS操作),解决了多线程并发操作导致数据不一致的问题,确保其他线程可以看到锁的真实状态。
继续阅读

False Sharing 伪共享 – 译

翻译自:http://mechanical-sympathy.blogspot.com/2011/07/false-sharing.html

伪共享

内存在缓存系统里是以缓存行为单位存储的。缓存行是大小为2的整数幂的连续字节,典型是32-256字节。最普遍的缓存行大小是64字节。伪共享是个术语,用于当多个线程不知不觉地相互影响对方的性能,在修改使用相同缓存行的依赖变量时。缓存行上的写冲突是并行执行线程在SMP(对称多处理器 )系统上获得伸缩性的单一最大制约因素。我已听到伪共享被描述为无声的性能杀手,因为在查看代码时,它非常不明显。

为了达到按线程数量的线性伸缩性,我们必须确保没有两个线程写同一个变量或同一个缓存行。两个线程写同一个变量可以在代码层面跟踪到。为了知道独立变量是否共享同一个缓存行,我们需要知道内存布局,或者让工具告诉我们。 Intel VTune是这样的一个分析工具。在这篇文章,我将解释Java对象的内存布局和如何通过填充缓存行来避免伪共享。
继续阅读