上一篇文章提到AQS是基于CLH lock queue,那么什么是CLH lock queue,说复杂很复杂说简单也简单, 所谓大道至简:

CLH lock queue其实就是一个FIFO的队列,队列中的每个结点(线程)只要等待其前继释放锁就可以了。

AbstractQueuedSynchronizer是通过一个内部类Node来实现CLH lock queue的一个变种,但基本原理是类似的。

在介绍Node类之前,我们来介绍下Spin Lock,通常就是用CLH lock queue来实现自旋锁,所谓自旋锁简单来说就是线程通过循环来等待而不是睡眠。 Talk 再多不如 show code:

class ClhSpinLock {
private final ThreadLocal<Node> prev;
private final ThreadLocal<Node> node;
private final AtomicReference<Node> tail = new AtomicReference<Node>(new Node()); public ClhSpinLock() {
this.node = new ThreadLocal<Node>() {
protected Node initialValue() {
return new Node();
}
}; this.prev = new ThreadLocal<Node>() {
protected Node initialValue() {
return null;
}
};
} public void lock() {
final Node node = this.node.get();
node.locked = true;
// 一个CAS操作即可将当前线程对应的节点加入到队列中,
// 并且同时获得了前继节点的引用,然后就是等待前继释放锁
Node pred = this.tail.getAndSet(node);
this.prev.set(pred);
while (pred.locked) {// 进入自旋
}
} public void unlock() {
final Node node = this.node.get();
node.locked = false;
this.node.set(this.prev.get());
} private static class Node {
private volatile boolean locked;
}
}

上面的代码中线程巧妙的通过ThreadLocal保存了当前结点和前继结点的引用,自旋就是lock中的while循环。 总的来说这种实现的好处是保证所有等待线程的公平竞争,而且没有竞争同一个变量,因为每个线程只要等待自己的前继释放就好了。 而自旋的好处是线程不需要睡眠和唤醒,减小了系统调用的开销。

public static void main(String[] args) {
final ClhSpinLock lock = new ClhSpinLock();
lock.lock(); for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
System.out.println(Thread.currentThread().getId() + " acquired the lock!");
lock.unlock();
}
}).start();
Thread.sleep(100);
} System.out.println("main thread unlock!");
lock.unlock();
}

上面代码的运行的结果应该跟上一篇文章中的完全一样。

ClhSpinLock的Node类实现很简单只有一个布尔值,AbstractQueuedSynchronizer$Node的实现稍微复杂点,大概是这样的:

     +------+  prev +-----+       +-----+
head | | <---- | | <---- | | tail
+------+ +-----+ +-----+
  • head:头指针
  • tail:尾指针
  • prev:指向前继的指针
  • next:这个指针图中没有画出来,它跟prev相反,指向后继

关键不同就是next指针,这是因为AQS中线程不是一直在自旋的,而可能会反复的睡眠和唤醒,这就需要前继释放锁的时候通过next 指针找到其后继将其唤醒,也就是AQS的等待队列中后继是被前继唤醒的。AQS结合了自旋和睡眠/唤醒两种方法的优点。

其中线程的睡眠和唤醒就是用到我下一篇文章将要讲到的LockSupport。

最后提一点,上面的ClhSpinLock类中还有一个关键的点就是lock方法中注释的地方:

一个CAS操作即可将当前线程对应的节点加入到队列中,并获取到其前继。

实际上可以说整个AQS框架都是建立在CAS的基础上的,这些原子操作是多线程竞争的核心地带,AQS中很多绕来绕去的代码都是为了 减少竞争。我会在后面AbstractQueuedSynchronizer源码分析中做详细介绍。

Java并发包源码学习之AQS框架(二)CLH lock queue和自旋锁的更多相关文章

  1. Java并发包源码学习之AQS框架(四)AbstractQueuedSynchronizer源码分析

    经过前面几篇文章的铺垫,今天我们终于要看看AQS的庐山真面目了,建议第一次看AbstractQueuedSynchronizer 类源码的朋友可以先看下我前面几篇文章: <Java并发包源码学习 ...

  2. Java并发包源码学习之AQS框架(三)LockSupport和interrupt

    接着上一篇文章今天我们来介绍下LockSupport和Java中线程的中断(interrupt). 其实除了LockSupport,Java之初就有Object对象的wait和notify方法可以实现 ...

  3. Java并发包源码学习之AQS框架(一)概述

    AQS其实就是java.util.concurrent.locks.AbstractQueuedSynchronizer这个类. 阅读Java的并发包源码你会发现这个类是整个java.util.con ...

  4. Java并发包源码学习系列:AQS共享式与独占式获取与释放资源的区别

    目录 Java并发包源码学习系列:AQS共享模式获取与释放资源 独占式获取资源 void acquire(int arg) boolean acquireQueued(Node, int) 独占式释放 ...

  5. Java并发包源码学习系列:CLH同步队列及同步资源获取与释放

    目录 本篇学习目标 CLH队列的结构 资源获取 入队Node addWaiter(Node mode) 不断尝试Node enq(final Node node) boolean acquireQue ...

  6. Java并发包源码学习系列:ReentrantLock可重入独占锁详解

    目录 基本用法介绍 继承体系 构造方法 state状态表示 获取锁 void lock()方法 NonfairSync FairSync 公平与非公平策略的差异 void lockInterrupti ...

  7. Java并发包源码学习系列:ReentrantReadWriteLock读写锁解析

    目录 ReadWriteLock读写锁概述 读写锁案例 ReentrantReadWriteLock架构总览 Sync重要字段及内部类表示 写锁的获取 void lock() boolean writ ...

  8. Java并发包源码学习系列:详解Condition条件队列、signal和await

    目录 Condition接口 AQS条件变量的支持之ConditionObject内部类 回顾AQS中的Node void await() 添加到条件队列 Node addConditionWaite ...

  9. Java并发包源码学习系列:挂起与唤醒线程LockSupport工具类

    目录 LockSupport概述 park与unpark相关方法 中断演示 blocker的作用 测试无blocker 测试带blocker JDK提供的demo 总结 参考阅读 系列传送门: Jav ...

随机推荐

  1. C#常用异常类记录

    从MSDN抄下来的 ArgumentException 传递到方法的非空参数是无效的. ArgumentNullException  传递给方法的参数为空. ArgumentOutOfRangeExc ...

  2. Python3导入cookielib失败

    Python 3 改成 http.cookiejar了,所以只要改成import http.cookiejar就自动导入cookiejar了,如果还是不行,就把所有的.pyc删掉试试.

  3. hdu 2042 - 不容易系列之二

    题意:(略)太长了 解法:找规律,发现A[i]=A[i-1]*2-2; 1: #include<stdlib.h> 2: #include<string.h> 3: #incl ...

  4. jQuery知识点总结(第一天)

    整理笔记前的题外话: 我认为互联网的高速发展其中最为主要的一点就是——开源. 开源精神,使得无数的IT爱好者贡献所学,将自己的智慧结晶无偿奉献给大家.后辈的学习者在混沌的互联网世界里,追寻着前辈们留下 ...

  5. HTML5系列三(多媒体播放、本地存储、本地数据库、离线应用)

    各浏览器对编码格式的支持情况 audio和video元素的属性介绍 1.src:媒体数据的URL地址 <video src="pr6.mp4"></video&g ...

  6. Web Worker

    写在前面 众所周知,JavaScript是单线程的,JS和UI更新共享同一个进程的部分原因是它们之间互访频繁,但由于共享同一个进程也就会造成js代码在运行的时候用户点击界面元素而没有任何响应这样的情况 ...

  7. codeforce B Island Puzzle

    B. Island Puzzle time limit per test 2 seconds memory limit per test 256 megabytes input standard in ...

  8. 回调函数通俗解析(之前看了很久都不理解,今天终于ok啦)

    自学jquery的时候,看到一英文词(Callback),顿时背部隐隐冒冷汗.迅速google之,发现原来中文翻译成回调.也就是回调函数了.不懂啊,于是在google回调函数,发现网上的中文解释实在是 ...

  9. Fix failed to start session in Ubuntu

    When you are at login, press Ctrl+Alt+F1. It will take you to command line interface from the GUI. I ...

  10. easyUI的控件

    CSS类定义: div easyui-window                               window窗口样式 属性如下: 1)       modal:是否生成模态窗口.tru ...