AQS

AQS 的全称是 Abstract Queued Synchronizer,也就是基于队列实现的抽象同步器

AQS 的主要功能

  • 同步状态的原子性管理。
  • 线程的阻塞和解除阻塞。
  • 提供阻塞线程的存储队列。

衍生功能

  • 通过中断实现的任务取消,基于线程中断实现。
  • 可选的超时设置,也就是调用者可以选择放弃等待。
  • 定义了Condition接口,用于支持管程形式的await/signal/signalAll操作,代替了Object类基于JNI提供的wait/notify/notifyAll。
AQS还根据同步状态的不同管理方式区分为两种不同的实现:独占状态的同步器和共享状态的同步器。

伪代码:

// 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;
}

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

  • 同步状态的原子性管理。
  • 等待队列的管理。
  • 线程的阻塞与解除阻塞。

CLH (Craig, Landin, and Hagersten) locks等待队列

CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程仅仅在本地变量上自旋,它不断轮询前驱的状态,假设发现前驱释放了锁就结束自旋。从实现上看,CLH锁是一种自旋锁,能确保无饥饿性,提供先来先服务的公平性。先看简单的CLH锁的一个简单实现:

public class CLHLock implements Lock {

    AtomicReference tail = new AtomicReference<>(new QueueNode());

    ThreadLocal pred;
    ThreadLocal current;

    public CLHLock() {
        current = ThreadLocal.withInitial(QueueNode::new);
        pred = ThreadLocal.withInitial(() -> null);
    }

    @Override
    public void lock() {
        QueueNode node = current.get();
        node.locked = true;
        QueueNode pred = tail.getAndSet(node);
        this.pred.set(pred);
        while (pred.locked) {
        }
    }

    @Override
    public void unlock() {
        QueueNode node = current.get();
        node.locked = false;
        current.set(this.pred.get());
    }

    static class QueueNode {

        boolean locked;
    }

    // 忽略其他接口方法的实现
}   

但是这个代码其实有点问题,不停的死循环会导致cpu过高,实际在实现的时候会采用释放锁的时候通知被阻塞线程的方式。

  • todo: 这里还有一些别的优化

线程阻塞与唤醒

线程的阻塞和唤醒在JDK1.5之前,一般只能依赖于Object类提供的wait()、notify()和notifyAll()方法,它们都是JNI方法,由JVM提供实现,并且它们必须运行在获取监视器锁的代码块内(synchronized代码块中),这个局限性先不谈性能上的问题,代码的简洁性和灵活性是比较低的。JDK1.5引入了LockSupport类,底层是基于Unsafe类的park()和unpark()方法,提供了线程阻塞和唤醒的功能,它的机制有点像只有一个允许使用资源的信号量java.util.concurrent.Semaphore,也就是一个线程只能通过park()方法阻塞一次,只能调用unpark()方法解除调用阻塞一次,线程就会唤醒(多次调用unpark()方法也只会唤醒一次),可以想象是内部维护了一个0-1的计数器。

CLH队列变体的实现
独占模式与共享模式
Condition的实现
实战篇

你可能感兴趣的:(AQS)