AQS2:可重入和阻塞
本文仅基于可重入的锁(ReentrantLock类)对AQS做分析,只考虑独占锁。
共享锁与独占锁的更多信息,以后再讨论。
AQS中队列的实现
节点Node
AQS的节点包含了对前置节点的引用pre,后置节点的引用next,以及持有节点的线程thread.
static final class Node { /**
pre节点,主要用来实现取消
* Link to predecessor node that current node/thread relies on
* for checking waitStatus. Assigned during enqueuing, and nulled
* out (for sake of GC) only upon dequeuing. Also, upon
* cancellation of a predecessor, we short-circuit while
* finding a non-cancelled one, which will always exist
* because the head node is never cancelled: A node becomes
* head only as a result of successful acquire. A
* cancelled thread never succeeds in acquiring, and a thread only
* cancels itself, not any other node.
*/
volatile Node prev; /**
next节点,主要用来实现阻塞/唤醒
* Link to the successor node that the current node/thread
* unparks upon release. Assigned during enqueuing, adjusted
* when bypassing cancelled predecessors, and nulled out (for
* sake of GC) when dequeued. The enq operation does not
* assign next field of a predecessor until after attachment,
* so seeing a null next field does not necessarily mean that
* node is at end of queue. However, if a next field appears
* to be null, we can scan prev's from the tail to
* double-check. The next field of cancelled nodes is set to
* point to the node itself instead of null, to make life
* easier for isOnSyncQueue.
*/
volatile Node next; //Node 节点 关联的线程
//1.重入时判断是否持有锁的线程
//2.取消阻塞时,unpark节点对应的线程
/**
* The thread that enqueued this node. Initialized on
* construction and nulled out after use.
*/
volatile Thread thread;
}
AQS的属性
CLH队列中只有tail节点,通过tail节点就可以实现首尾相连的队列。
AQS中则使用head和tail节点实现队列。
入队时,通过tail节点构建CLH队列
出队时,只需要设置head节点(把当前成功获取锁的节点设置为head,则head节点为持有锁的节点)
入队:
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
//尝试直接设置tail入队,失败了走完整的enq入队逻辑
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
//注意:如果没有调用过enq tail是为空的,下面的判断不成立
//也就是说,enq方法调用之后,尝试直接设置tail入队,才有机会
if (pred != null) {
node.prev = pred;
//如果入队成功,直接返回
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//完整的入队逻辑
enq(node);
return node;
} /**
自旋入队(包含了初始化流程)
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
//初始化
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
//入队成功则返回,不成功继续自旋,最终生成队列
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
考虑enq不断自旋的情况:
线程1
1.第1次循环
tail为空,初始化
tail=head = new Node(无参构造,空的Node)
+------+
head(tail) | |
+------+
没有return,继续循环
2.第2次循环
如果成功入队
+------+ prev +-----+
head | | <---- | | tail(当前节点)
+------+ +-----+
返回前置节点,结束自旋
线程2
3.第1次循环
+------+ prev +-----+ +-----+
head | | <---- | | <---- | | tail
+------+ +-----+ +-----+
可重入
AQS为了支持可重入,使用了计数方式,对应属性是 volatile int state.
//AQS的同步状态
/**
* The synchronization state.
*/
private volatile int state;
①某个线
程获取锁的标志就是原子性的把state从某个期望状态(expect)改为指定状态(target)。
如果修改的时候state!=expect,说明state被其他线程改动了,其语义就是:锁被其他线程持有。
当前线程只能自旋/阻塞,直到持有锁的线程把状态改回expect,当前线程才能结束自旋,并且持有锁。
②支持可重入,则需要计数机制,每次重入,state+1,释放则state-1.
阻塞/唤醒
AQS使用LockSupport来阻塞/唤醒线程。
LockSupport针对每个线程操作,发给每个线程一个许可(permit),与blocker无关。
//如果得到许可,则立即返回,否则阻塞当前线程
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
UNSAFE.park(false, 0L);
setBlocker(t, null);
} //给指定传入的线程许可,解除阻塞
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
LockSupport不能被滥用。对当前线程调用unpark,可能导致AQS产生不可预知的情况。
park的解除条件
- 其他线程对当前线程调用了unpark
- 其他线程中断了当前线程
- 不可预知的返回
只考虑可重入和阻塞的AQS的简单实现
import com.google.common.collect.Lists; import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.locks.LockSupport; /**
* Created by 张三丰 on 2017\8\2 0002.
*/
public class ReentanceAndBlock { static class CLHLock {
static class Node {
/**
* 节点的锁定状态,使用阻塞机制后,不需要这个状态了,获取不到锁的线程被阻塞,并在合适的时机被唤醒
*/
// volatile boolean isLocked = true;
//下一个节点
volatile Node next;
//持有节点的线程
volatile Thread thread; public Node() {
} public Node(Thread thread) {
this.thread = thread;
}
} //可重入的状态,aqs使用了int 类型,然后使用了unsafe做cas操作。
// 这里不涉及unsafe,所以使用AtomicInteger
private volatile AtomicInteger state = new AtomicInteger(); private volatile Node tailNode; private volatile Node headNode; //这个值只需要持有锁的线程自己可见即可(锁重入时判断,只能是同一个线程)
// ,不需要volatile (其他线程见不见不影响)
private Thread holdLockThread; private static final AtomicReferenceFieldUpdater<CLHLock, Node> TAIL_UPDATER =
AtomicReferenceFieldUpdater.newUpdater(CLHLock.class, Node.class, "tailNode"); private static final AtomicReferenceFieldUpdater<CLHLock, Node> HEAD_UPDATER =
AtomicReferenceFieldUpdater.newUpdater(CLHLock.class, Node.class, "headNode"); public void lock(int acquire) {
//1.尝试获取锁
if (tryLock(acquire)) {
return;
}
//2.入队
Node current = null;
for (; ; ) {
Node t = tailNode;
if (t == null) {
Node node = new Node();
if (TAIL_UPDATER.compareAndSet(this, null, node)) {
//初始化时,head节点是一个假的节点,绑定的是当前持有锁的线程
//以后也会一直保持绑定持有锁的节点
headNode = tailNode;
}
} else {
Node node = new Node(Thread.currentThread());
if (TAIL_UPDATER.compareAndSet(this, t, node)) {
t.next = node;
current = node;
break;
}
}
}
//3.自旋获取锁,或阻塞后等待唤醒
for (; ; ) {
//头节点是持有锁的节点,检查头节点,竞争锁
if (headNode.next == current) {
//前置节点是头节点,不能阻塞,一直自旋尝试获取锁
//这种情况是为了防止,被唤醒后,恰好有另外一个线程获取了锁,竞争失败的情况
if (tryLock(acquire)) {
headNode = current;
break;
}
// System.out.println("自旋获取锁~~");
} else {
//打印队列数
// System.out.println("====" + __getCount());
//阻塞当前线程,等待前置节点唤醒
LockSupport.park(this);
}
}
} /**
* 获取队列数
*
* @return
*/
private int __getCount() {
int count = 1;
Node node = headNode.next;
while (node != null) {
count++;
node = node.next;
}
return count;
} /**
* 定义获取锁的逻辑,AQS中此类由具体子类实现
* @param acquire
* @return
*/
private boolean tryLock(int acquire) {
int state = this.state.get();
if (state == 0 && this.state.compareAndSet(0, acquire)) {
//获取锁成功,不需要入队
holdLockThread = Thread.currentThread();
// __print1();
return true;
} else if (holdLockThread == Thread.currentThread()) {
//线程重入
this.state.set(this.state.get() + acquire);
return true;
}
return false;
} private void __print1() {
if (headNode != null) {
Node node = headNode.next;
boolean isInCLH = false;
while (node != null) {
if (node.thread == Thread.currentThread()) {
isInCLH = true;
break;
}
node = node.next;
}
if (!isInCLH) {
System.out.println("厉害了,在唤醒的线程之前抢到了锁");
}
}
} /**
*
*/
public void unlock(int acquire) {
// System.out.println("unlock" + Thread.currentThread());
if (tryRelease(acquire)) {
holdLockThread = null;
state.set(0);
unparkNext();
}
} /**
* 定义释放锁的逻辑,AQS中此类由具体子类实现
* @param acquire
* @return
*/
private boolean tryRelease(int acquire) {
//只有持有锁的线程才能释放锁
if (Thread.currentThread() != holdLockThread) {
System.out.println("不是hold" + holdLockThread);
return false;
}
int newValue = state.get() - acquire;
//不会执行到
if (newValue < 0) {
throw new RuntimeException("state < 0");
}
//减去计数
state.set(newValue);
return newValue == 0;
} private void unparkNext() {
Node headNode = this.headNode;
if (headNode != null) {
Node next = headNode.next;
if (next != null) {
//唤醒下个节点
LockSupport.unpark(next.thread);
}
}
}
} static class CLHTester {
public static void main(String[] args) throws InterruptedException {
testLockAndUnlock();
testReentrance();
} private static void testReentrance() {
System.out.println("testReentrance");
final Long[] number = {0L};
ExecutorService executorService = Executors.newFixedThreadPool(10);
CLHLock lock = new CLHLock();
for (int i = 0; i < 100; i++) {
executorService.execute(() -> {
lock.lock(1);
Long n = number[0];
if (n == 50) {
System.out.println("重入之前计数:" + lock.state);
System.out.println("锁重入2");
lock.lock(2);
System.out.println("重入之后计数:" + lock.state);
lock.unlock(2);
System.out.println("锁释放2");
System.out.println("释放之后计数:" + lock.state);
}
number[0] = n + 1;
System.out.println(number[0]);
lock.unlock(1);
});
}
executorService.shutdown();
} private static void testLockAndUnlock() {
System.out.println("testLockAndUnlock");
//测试锁,循环100次,要求多线程共享的值能按照顺序+1,最终得到正确的结果 100
List<Integer> sharedValue = Lists.newArrayList(new Integer(0));
ExecutorService executorService = Executors.newFixedThreadPool(50);
CLHLock lock = new CLHLock();
for (int i = 0; i < 100; i++) {
executorService.execute(() -> {
lock.lock(1);
//把数据+1
sharedValue.add(0, sharedValue.get(0) + 1);
//输出的结构必然是按顺序的
System.out.println(sharedValue.get(0));
// System.out.println(sharedValue.get(0) + "=========" + Thread.currentThread().toString());
lock.unlock(1);
});
}
executorService.shutdown();
while (!executorService.isTerminated()) { }
}
}
}
AQS2:可重入和阻塞的更多相关文章
- Android 死锁和重入锁
死锁的定义: 1.一般的死锁 一般的死锁是指多个线程的执行必须同时拥有多个资源,由于不同的线程需要的资源被不同的线程占用,最终导致僵持的状态,这就是一般死锁的定义. package com.cxt.t ...
- java并发编程(一)可重入内置锁
每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁或监视器锁.线程在进入同步代码块之前会自动获取锁,并且在退出同步代码块时会自动释放锁.获得内置锁的唯一途径就是进入由这个锁保护的同步代码块 ...
- java ReentrantLock可重入锁功能
1.可重入锁是可以中断的,如果发生了死锁,可以中断程序 //如下程序出现死锁,不去kill jvm无法解决死锁 public class Uninterruptible { public static ...
- linux可重入、异步信号安全和线程安全
一 可重入函数 当一个被捕获的信号被一个进程处理时,进程执行的普通的指令序列会被一个信号处理器暂时地中断.它首先执行该信号处理程序中的指令.如果从信号处理程序返回(例如没有调用exit或longjmp ...
- Java 重入锁 ReentrantLock
本篇博客是转过来的. 但是略有改动感谢 http://my.oschina.net/noahxiao/blog/101558 摘要 从使用场景的角度出发来介绍对ReentrantLock的使用,相对来 ...
- QThread 与 QObject的关系(QObject可以用于多线程,可以发送信号调用存在于其他线程的slot函数,但GUI类不可重入)
QThread 继承 QObject..它可以发送started和finished信号,也提供了一些slot函数. QObject.可以用于多线程,可以发送信号调用存在于其他线程的slot函数,也可以 ...
- ReentrantLock可重入锁的使用场景(转)
摘要 从使用场景的角度出发来介绍对ReentrantLock的使用,相对来说容易理解一些. 场景1:如果发现该操作已经在执行中则不再执行(有状态执行) a.用在定时任务时,如果任务执行时间可能超过下次 ...
- java synchronized内置锁的可重入性和分析总结
最近在读<<Java并发编程实践>>,在第二章中线程安全中降到线程锁的重进入(Reentrancy) 当一个线程请求其它的线程已经占有的锁时,请求线程将被阻塞.然而内部锁是可重 ...
- Java多线程——深入重入锁ReentrantLock
简述 ReentrantLock 是一个可重入的互斥(/独占)锁,又称为“独占锁”. ReentrantLock通过自定义队列同步器(AQS-AbstractQueuedSychronized,是实现 ...
随机推荐
- HexChat访问Docker频道
1.使用HexChat登录Freenode.net 2.在Freenode下输入并回车: /msg NickServ REGISTER yourpassword youremail@example.c ...
- android中的Touch研究
android中的事件类型分为按键事件和屏幕触摸事件,Touch事件是屏幕触摸事件的基础事件,有必要对它进行深入的了解. 一个最简单的屏幕触摸动作触发了一系列Touch事件:ACTION_DOWN-& ...
- JS 用正则表达式,验证密码包含数字和字母的方法
必须包含至少一位数字和一位字母,脚本方法如下: function CheckPassWord(password) {//密码必须包含数字和字母 var str = password; if (str ...
- 在NuoDB上运行Asterisk
欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文来自云+社区翻译社,作者Hans362 您可能已经熟悉Asterisk,一个广泛部署的开源Telephony框架.如果你不太熟悉,你应该 ...
- 使用ANY、Some或All关键字
可以使用All或Any关键字修改引入子查询的比较运算符.Some是与Any等效的ISO标准,All要求Where表达式与子查询返回的每个值进行比较时都应满足比较条件,Any则要求Where表达式与子查 ...
- JavaWeb技术
1.简介 Java Web是用Java技术来解决相关web互联网领域的技术总和.web包括:web服务器和web客户端两部分.Java在客户端的应用有java applet,不过使用得很少,Java在 ...
- A bug about RecipientEditTextView
- Steps to reproduce the problem. Pre-condition:the threshold of the RecipientEditTextView is set to ...
- 通过tomcat shutdown port关闭tomcat
在tomcat server.xml配置文件中,有个配置项 <Server port="8005" shutdown="SHUTDOWN"> 通过向 ...
- javascript对象(3)
这个对象,不是那个对象,第三哦! 对象之间会存在继承,所以,来说一下他们之间存在的三种三种继承方式: 1.冒用继承 //创建了孙悟空构造函数 function Sun(change,weapon,gf ...
- keras 自定义 custom 函数
转自: https://kexue.fm/archives/4493/,感谢分享! Keras是一个搭积木式的深度学习框架,用它可以很方便且直观地搭建一些常见的深度学习模型.在tensorflow出来 ...