JDK里的自旋锁

Wesley13
• 阅读 555

自旋锁是采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时才能进入临界区。

JDK里面自旋锁的实现有 SynchronousQueue  和 LinkedTransferQueue。  本文只是自己对源码的简单理解。

先说公平锁,先等待的线程先获得数据。SynchronousQueue的内部类TransferQueue实现了公平锁。

某一时刻 线程A看到内存的情况如下:   链表,head 和 tail 分别指向链首和链尾,并且线程执行了ht = tail 。

* * head ht=tail * | | * v v * M -> U -> U -> U *

M 代表match , 表示该节点已经匹配到了数据,  poll 等到了 offer ,或者 offer 等到了 poll 。

U 代表unmatch , 表示该节点在等待填充数据或者数据等待poll 。

由于并发操作,经过其他线程的操作使得链表变成如下:(此操作是瞬间完成的)

* head ht tail * | |         | * v v v * M -> M -> U -> U -> U -> U *

有两个U节点添加进来, 第二个节点M匹配到了数据。

假设此时线程A想再往链表添加U节点:

那么需要进行那些操作?

1: 发现ht != tail , 于是需要继续循环

2:在 ht == tail 的情况下需要先 判断 tail.next 是否为null , 如果不是则继续循环

3:新建一个node,将 tail.next 指向新node。

4:将U赋值给tail 。    (如果只执行了3而还没有执行4,就会出现2中的情况,ht=tail,但是ht.next 为空,说明此时有线程执行了3操作但是还没有执行4操作,此时只需要继续循环即可)

* head ht=tail * | | * v v * U -> U -> U -> U -> U

/* * head tail * | | * v v * U -> U -> U -> U -> U * */

5:  自旋等待match。

6: 等待结束有两种可能结果: 6.1 超时取消    6.2 成功  

成功时: 因此只需将 head 指向node.next . 并且将 node.next 置空。 虽然此时   M -> M  但是只要其它线程正确的执行了M.next = null 即可。

/* * head tail * | | * v v * M M M M M M -> M -> U -> U - > U ->U * */

/* * head tail * | | * v v * M M M M M M -> M U -> U - > U ->U * */

等待超时:此时U按理也应该来到了链首,前面node的也都超时了,但万一其他的线程没有获得cpu呢,就会出现如下的状况,需要将U前面的几个node也顺便清理掉

/* * head tail * | | * v v * M M M M M U -> U -> U -> U - > U ->U * */

假设此时线程A是想将U变成M,这个逻辑很简单,按顺序找到一个U,尝试给U的item赋值。成功结束,不成功继续循环。

E transfer(E e, boolean timed, long nanos) {

QNode s = null; // constructed/reused as needed boolean isData = (e != null); for (;;) { QNode t = tail; QNode h = head; if (t == null || h == null) // saw uninitialized value continue; // spin if (h == t || t.isData == isData) { // empty or same-mode QNode tn = t.next; if (t != tail) // inconsistent read continue; if (tn != null) { // lagging tail advanceTail(t, tn); continue; } if (timed && nanos <= 0) // can't wait return null; if (s == null) s = new QNode(e, isData); if (!t.casNext(null, s)) // failed to link in continue; advanceTail(t, s); // swing tail and wait Object x = awaitFulfill(s, e, timed, nanos); if (x == s) { // wait was cancelled clean(t, s); return null; }

        if (!s.isOffList()) {           // not already unlinked

advanceHead(t, s); // unlink if head if (x != null) // and forget fields s.item = s; s.waiter = null; } return (x != null) ? (E)x : e; } else { // complementary-mode QNode m = h.next; // node to fulfill if (t != tail || m == null || h != head) continue; // inconsistent read Object x = m.item; if (isData == (x != null) || // m already fulfilled x == m || // m cancelled !m.casItem(x, e)) { // lost CAS advanceHead(h, m); // dequeue and retry continue; }

        advanceHead(h, m); // successfully fulfilled

LockSupport.unpark(m.waiter); return (x != null) ? (E)x : e; } } }

自旋分析:

Object awaitFulfill(QNode s, E e, boolean timed, long nanos) { /* Same idea as TransferStack.awaitFulfill */ final long deadline = timed ? System.nanoTime() + nanos : 0L; Thread w = Thread.currentThread(); int spins = ((head.next == s) ? (timed ? maxTimedSpins : maxUntimedSpins) : 0); for (;;) { if (w.isInterrupted()) s.tryCancel(e); Object x = s.item; if (x != e) return x; if (timed) { nanos = deadline - System.nanoTime(); if (nanos <= 0L) { s.tryCancel(e); continue; } } if (spins > 0) --spins; else if (s.waiter == null) s.waiter = w; else if (!timed) LockSupport.park(this); else if (nanos > spinForTimeoutThreshold) LockSupport.parkNanos(this, nanos); } }

循环判断 node.item 有没有变化,如果有变化则匹配成功,如果没有则继续循环, 循环一定的次数(spins)后还没有匹配成功则使用 LockSupport.park() 来阻塞线程。这个循环过程即可称为自旋。

但是公平锁存在一个问题:

/* * * head ht=tail * | | * v v * M -> U -> U -> U * * | * | * U变成M,但是此线程并没有获得cpu,没法立即执行,于是后面获得cpu的线程便有了意见,为啥不将数据给我,我能立即执行。 */

要想解决此问题说来也十分简单,每个线程只需不停的将自己的node和head进行交换(casHead)即可优先获得task。 这个是在 TransferStack中实现的,说起来简单,但实现起来则困难得多。其实这也是经常听到的抢占式执行吧,SynchronousQueue 默认使用非公平锁。

public SynchronousQueue() { this(false); } public SynchronousQueue(boolean fair) { transferer = fair ? new TransferQueue<E>() : new TransferStack<E>(); }

点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
java中的锁
记录一下公平锁,非公平锁,可重入锁(递归锁),读写锁,自旋锁的概念,以及一些和锁有关的java类。公平锁与非公平锁:公平锁就是在多线程环境下,每个线程在获取锁时,先查看这个锁维护的队列,如果队列为空或者自身就是等待队列的第一个,就占有锁。否则就加入到等待队列中,按照FIFO的顺序依次占有锁。非公平锁会一上来就试图占
Easter79 Easter79
2年前
synchronized在jdk1.6之后引入的一些优化方案
自旋锁    jdk1.6之后默认开启,可以使用参数XX:UseSpinning控制,自旋等待不能代替阻塞,且先不说对处理器数量的要求,自旋等待本身虽然避免了线程切换的开销,但它是要占用处理器时间的,因此,如果锁被占用的时间很短,自旋等待的效果就会非常好,反之,如果锁被占用的时候很长,那么自旋的线程只会白白消耗处理器资源,而不会做任何有用的工
Stella981 Stella981
2年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这