aqs clh java_Java并发包源码学习之AQS框架(二)CLH lock queue和自旋锁

上一篇文章提到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:

classClhSpinLock {private final ThreadLocalprev;private final ThreadLocalnode;private final AtomicReference tail = new AtomicReference(newNode());publicClhSpinLock() {this.node = new ThreadLocal() {protectedNode initialValue() {return newNode();

}

};this.prev = new ThreadLocal() {protectedNode initialValue() {return null;

}

};

}public voidlock() {final Node node = this.node.get();

node.locked= true;//一个CAS操作即可将当前线程对应的节点加入到队列中,//并且同时获得了前继节点的引用,然后就是等待前继释放锁

Node pred = this.tail.getAndSet(node);this.prev.set(pred);while (pred.locked) {//进入自旋

}

}public voidunlock() {final Node node = this.node.get();

node.locked= false;this.node.set(this.prev.get());

}private static classNode {private volatile booleanlocked;

}

}

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

public static voidmain(String[] args) {final ClhSpinLock lock = newClhSpinLock();

lock.lock();for (int i = 0; i < 10; i++) {new Thread(newRunnable() {

@Overridepublic voidrun() {

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| |

head:头指针

tail:尾指针

prev:指向前继的指针

next:这个指针图中没有画出来,它跟prev相反,指向后继

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

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

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值