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,是实现 ...
随机推荐
- Farey Sequence(欧拉函数板子题)
题目链接:http://poj.org/problem?id=2478 Farey Sequence Time Limit: 1000MS Memory Limit: 65536K Total S ...
- 8086实时时钟实验(二)——《x86汇编语言:从实模式到保护模式》读书笔记06
上次我们说了代码,这次我们说说怎样看到实验结果. 首先编译源文件(我的源文件就在当前路径下,a盘和c盘在上一级目录下): nasm -f bin c08_mbr.asm -o c08_mbr.bin ...
- innosetup的静默安装与卸载
静默安装,就是减少程序与用户的交互,一站式的安装过程(一气呵成) 1. 静默安装参数 innosetup的静默安装是通过参数来控制的 1.1. /silent ...
- a标签的 onclick 和 href 哪个先执行?
以下这种写法,onclick 事件先执行, href 属性下的动作后执行(页面跳转或 javascript 伪链接),如果不想执行 href 属性下的动作,onclick 需要返回 false. &l ...
- 在 Flask 应用中使用 gevent
在 Flask 应用中使用 gevent 普通的 flask 应用 通常在用 python 开发 Flask web 应用时,使用 Flask 自带的调试模式能够给开发带来极大便利.Flask 自带的 ...
- solidity语言
IDE:Atom 插件:autocomplete-solidity 代码自动补齐 linter-solium,linter-solidity代码检查错误 language-ethereum支持 ...
- Redis的Publish/Subscribe
Publish/Subscribe 从字面上理解就是发布(Publish)与订阅(Subscribe),在Redis中,你可以设定对某一个key值进行消息发布及消息订阅,当一个key值上进行了消息发布 ...
- pId的数据结构转children 数据结构(JS);
在工作中经常遇到需要把带有pId的的list数据转换为children格式的树形结构,一直都没有找到太好的工具函数.偶然间看到了这个函数,研究了下,感觉这个函数很强大,所以记录下来,作为备用,同时也贴 ...
- Java入门到精通——框架篇之Spring源码分析Spring两大核心类
一.Spring核心类概述. Spring里面有两个最核心的类这是Spring实现最重要的部分. 1.DefaultListableBeanFactory 这个类位于Beans项目下的org.spri ...
- JVM Guide
Java Virtual Machine: the Essential Guide October 8th, 2014 - By Alexey Zhebel Introduction Java Vir ...