java队列加锁_java并发-----浅析ReentrantLock加锁,解锁过程,公平锁非公平锁,AQS入门,CLH同步队列...

前言

为什么需要去了解AQS,AQS,AbstractQueuedSynchronizer,即队列同步器。它是构建锁或者其他同步组件的基础框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore等),JUC并发包的作者(Doug Lea)期望它能够成为实现大部分同步需求的基础。它是JUC并发包中的核心基础组件

本文所有源码基于JDK9

目的:掌握大概的流程/框架

适用人群:初学者想了解些源码的

不适合:想深入了解的

ReentrantLock-非公平锁

我们在实际中一定会用到ReentrantLock的lock操作,那么它的实现究竟是怎样的?我们以重入锁作为切入点。

1、构建锁,获得锁对象

//锁的声明

private final Sync sync;

// 构造锁,默认非公平

public ReentrantLock() {

sync = new NonfairSync();

}

2、lock方法,调用sync这个锁对象

public void lock() {

sync.acquire(1);

}

// 来自AQS

public final void acquire(int arg) {

if (!tryAcquire(arg) &&

acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

selfInterrupt();

}

关键性的四个方法:

1、tryAcquire:去尝试获取锁,获取成功则设置锁状态并返回true,否则返回false。该方法自定义同步组件自己实现,该方法必须要保证线程安全的获取同步状态。

2、addWaiter:如果tryAcquire返回FALSE(获取同步状态失败),则调用该方法将当前线程加入到CLH同步队列尾部。

3、acquireQueued:当前线程会根据公平性原则来进行阻塞等待(自旋),直到获取锁为止;并且返回当前线程在等待过程中有没有中断过。

4、selfInterrupt:产生一个中断。

tryAcquire

/**

* Sync object for non-fair locks

*/

static final class NonfairSync extends Sync {

private static final long serialVersionUID = 7316153563782823691L;

protected final boolean tryAcquire(int acquires) {

return nonfairTryAcquire(acquires);

}

}

加锁,是通过NonfairSync的这个方法实现的,但是NofairSync并没有它的实际代码。真正实现的是它的父类Sync。

nonfairTryAcquire(int acquires)

final boolean nonfairTryAcquire(int acquires) {

final Thread current = Thread.currentThread();

int c = getState();

if (c == 0) {

if (compareAndSetState(0, acquires)) {

setExclusiveOwnerThread(current);

return true;

}

}

else if (current == getExclusiveOwnerThread()) {

int nextc = c + acquires;

if (nextc < 0) // overflow

throw new Error("Maximum lock count exceeded");

setState(nextc);

return true;

}

return false;

}

getState(),这个方法在AQS中,用户获取锁的状态值。

/**

* Returns the current value of synchronization state.

* This operation has memory semantics of a {@code volatile} read.

*@return current state value

*/

protected final int getState() {

return state;

}

下面来解释一下state在独占锁的作用是什么,结合下nonfairTryAcquire(int acquires)。

首先第一个if条件,state = 0 的时候代表着,当前锁没有被某个线程占用,然后通过CAS操作设置线程的状态为值1,并且把当前线程设置为独占锁的拥有者。

第二个if,当state!=0的时候,代表这个锁已经被别的线程占着了,就判断,这个是不是这个锁的拥有者,如果是的话,锁state就+1,这就巧妙地设置了重入锁的。每次+1的机制

CLH同步队列

在介绍addWaiter前,先来看一下CLH同步队列,就看着大佬们在说CLH但是少有人说他是啥,我解释一下啊

The wait queue is a variant of a “CLH” (Craig, Landin, and Hagersten) lock queue. CLH locks are normally used for spinlocks

1、名称:CLH 由三个人名字组成 (Craig, Landin, and Hagersten)

2、基本数据结构:基于FIFO双端链表

3、用途:用于等待资源释放的队列。也就是等待锁释放的队列

注意:由于笔者是初学者,觉得CLH队列中的等待状态转换略微复杂,故意跳过。只看它们的方法,并没有特别深入

addWaiter

/**

* 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(mode);

for (;;) {

Node oldTail = tail;

if (oldTail != null) {

node.setPrevRelaxed(oldTail);

if (compareAndSetTail(oldTail, node)) {

oldTail.next = node;

return node;

}

} else {

initializeSyncQueue();

}

}

}

与JDK8不同的地方是,addWaiter直接使用自旋(无限循环)去完成入队的操作,而不是调用enq,实现的内容和enq差不多。下面给出enq的代码

/**

* Inserts node into queue, initializing if necessary. See picture above.

*@param node the node to insert

*@return node's predecessor

*/

private Node enq(Node node) {

for (;;) {

Node oldTail = tail;

if (oldTail != null) {

node.setPrevRelaxed(oldTail);

if (compareAndSetTail(oldTail, node)) {

oldTail.next = node;

return oldTail;

}

} else {

initializeSyncQueue();

}

}

}

注意addWaiter的返回值,是新加入的节点下面有用

acquireQueued

final boolean acquireQueued(final Node node, int arg) {

try {

// 中断标识

boolean interrupted = false;

// 自旋

for (;;) {

final Node p = node.predecessor();

// 如果当前线程节点的前驱节点是头结点,并且尝试获得锁成功

if (p == head && tryAcquire(arg)) {

setHead(node);

p.next = null; // help GC

return interrupted;

}

// 如果获取锁失败了,就进入挂起。

if (shouldParkAfterFailedAcquire(p, node) &&

parkAndCheckInterrupt())

interrupted = true;

}

} catch (Throwable t) {

cancelAcquire(node);

throw t;

}

}

关于挂起和唤醒,就先不看了,主要还是理解下流程。过多的细节会拖慢新手的学习之路,一定要记住这个!!

RenentrantLock-公平锁

protected final boolean tryAcquire(int acquires) {

final Thread current = Thread.currentThread();

int c = getState();

if (c == 0) {

if (!hasQueuedPredecessors() &&

compareAndSetState(0, acquires)) {

setExclusiveOwnerThread(current);

return true;

}

}

else if (current == getExclusiveOwnerThread()) {

int nextc = c + acquires;

if (nextc < 0)

throw new Error("Maximum lock count exceeded");

setState(nextc);

return true;

}

return false;

}

}

公平锁和非公平锁在于第一个判断条件中的,hasQueuedPredecessors()这个方法。

public final boolean hasQueuedPredecessors() {

Node t = tail;

Node h = head;

Node s;

// 头节点不是尾节点

// 第一个节点不为空

// 当前节点是头节点

return h != t &&

((s = h.next) == null || s.thread != Thread.currentThread());

}

从源码我们可以验证,公平和非公平的标准是否是按照队列的顺序进行锁的获取的原理。

小结:

到这里,加锁基本上就是结束了,我们忽略了挂起和唤醒这种复杂的操作。加锁的操作中,阻塞队列是由AQS使用CLH进行维护的。ReentrantLock,的同步操作主要还是依赖于AQS。

释放锁

public void unlock() {

sync.release(1);

}

public final boolean release(int arg) {

if (tryRelease(arg)) {

Node h = head;

if (h != null && h.waitStatus != 0)

unparkSuccessor(h);

return true;

}

return false;

}

protected final boolean tryRelease(int releases) {

int c = getState() - releases;

if (Thread.currentThread() != getExclusiveOwnerThread())

throw new IllegalMonitorStateException();

boolean free = false;

if (c == 0) {

free = true;

setExclusiveOwnerThread(null);

}

setState(c);

return free;

}

调用Sync的tryRelease,减小state值,然后uppark进行唤醒下一个节点PS,笔者忽略了唤醒的操作。

总结

本文针对和我一样刚入门不久的新手,从整体上以重入锁的加锁和解锁,去了解了一些AQS的CLH队列的基本内容,未涉及深层次,顺便看了下公平和非公平的实现。算作是一种了解把,个人感觉太细节的东西,新手看了也没用。还是从宏观上把握把握,会用,然后了解点源码,方便以后再来学习。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/506573.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java resttemplate_java-通过resttemplate通过Spring Rest服务发送文...

标题可能看起来很普通,但是没有一个适合我的问题.我有一个REST服务,它接受多部分形式的常规参数和文件.我想使用resttemplate将数据和文件发送到上述rest服务.直到我发送正常的字符串数据为止,没有任何问题.一旦我添加了发送字节的代码,那么我开始收到400错误的请求错误.如果我…

java中string的方法_java中String的常用方法

package com.string;public class string1 {public static void main(String args[]){//将char[]数组转换成Stringchar[] ch{h,我,是,中,国,人};String strnew String(ch);System.out.println(str); //结果为str"我是中国人"//将字符串转换为char数组,方法一使用getC…

Java是否为回文_java语言判断一个数字是否为回文数字

判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。示例 1:输入: 121输出: true示例 2:输入: -121输出: false解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。示例 3:输入: 10输出: false解释: 从右向左读, 为…

java floyd_百度百科里面的floyd算法java的代码,总是无法运行。请问是代码有问题吗,如何编译啊?...

展开全部不能编译运行的说法是错误&#xff0c;但是结果是否正确&#xff0c;我就32313133353236313431303231363533e59b9ee7ad9431333335303539不知道了&#xff0c;我不懂这个算法public class FLOYD {int[][] length null;// 任意两点之间路径长度int[][][] path null;// …

cache数据库和mysql_并发环境下,先操作数据库还是先操作缓存?

原标题&#xff1a;并发环境下&#xff0c;先操作数据库还是先操作缓存&#xff1f;来源&#xff1a;捡田螺的小男孩前言在分布式系统中&#xff0c;缓存和数据库同时存在时&#xff0c;如果有写操作&#xff0c;先操作数据库还是先操作缓存呢&#xff1f;本文将分5种方案 展开…

contab 手动可以 java_crontab 定时执行脚本出错,但手动执行脚本正常

原因&#xff1a; crontab 没有去读环境变量&#xff0c;需要再脚本中手动引入环境变量&#xff0c;可以用source 也可以用export 写死环境变量。为了定时监控Linux系统CPU、内存、负载的使用情况&#xff0c;写了个Shell脚本&#xff0c;当达到一定值得时候&#xff0c;发送邮…

java的mybatis批量更新_mybatis批量更新的问题

一、问题描述场景描述&#xff1a;有这样一个service方法&#xff0c;调用了两个dao中的方法。第一个方法按照传入的id批量更新用户名。第二个dao方法无数据库操作&#xff0c;仅仅抛出一个RuntimeException.这个service方法通过xml配置由spring事务管理的。两个DAO类中分别有S…

java 查看垃圾收集器_JVM系列:查看JVM使用的什么垃圾收集器

一、方法一打印虚拟机所有参数[rootlocalhost ~]# java -XX:PrintFlagsFinal -version | grep :uintx InitialHeapSize : 258689024 {product}uintx MaxHeapSize : 4139778048 {product}bool PrintFlagsFinal : true {product}bool UseCompressedOops : true {lp64_product}boo…

java在W n8安装_在windows中安装JDK8并配置环境变量-java环境变量设置

学习JAVA&#xff0c;必须得安装一下JDK(Java development kit java开发工具包)&#xff0c;配置一下环境就可以学习JAVA了&#xff0c;下面是下载和安装JDK的教程&#xff1a;一、去oracle官网上下载jdk8的下载地址&#xff1a;https://www.oracle.com/technetwork/java/javas…

lisp java_从Java调用的LISP代码

长篇小说:我正在为我的函数编程类做一个项目,我想到在Lisp中为Mario AI competition.我正在研究从Java调用LISP代码的框架/库/方式,甚至更好的LISP Java互通信。我看过Jacol但它是旧的,对我来说也不是很好。到目前为止,我的最佳选择是:Jatha.它真的很整洁,虽然一些Lisp构造还没…

java将图片上传数据库_〔技巧实例〕轻松实现将上传图片到数据库

很久就想自己写一写程序了&#xff0c;不过由于赖就不想写我&#xff0c;今天刚好有空&#xff0c;所以写了这个小小的程序很容易一看就知道的&#xff0c;不多说了就此开始&#xff1a;我们做一个上传的。数据据库的字段就id自动编号 big 字段类型是 OLE 呵呵就简单的那个字段…

mysql带参数的sql_MySql存储过程是带参数的存储过程(动态执行SQL语句)

下文介绍的MySql存储过程是带参数的存储过程(动态执行SQL语句)&#xff0c;该MySql存储过程是根据用户输入的条件和排序方式查询用户的信息&#xff0c;排序条件可以没有调用方式&#xff1a;call GetUsersDynamic(age<30,);/********动态查询用户的信息********/CREATE PRO…

java 注释 depredated_depredated是什么意思_depredated怎么读_depredated翻译_用法_发音_词组_同反义词-新东方在线英语词典...

双语例句1.Theentireareahasbeendepredatedinthewar.整个地区在战争中都遭到破坏。2.WehopethatHaitiwhich washeavilydepredatedby the killerquakewillbereconstructedintheforseeablefuture.我们希望在大地震中受重创的海地在不久的将来可以重建。3.TwoIssuesonAddresseesDe…

java计算雷达扫描范围_雷达扫描 - linyinmobayu - 博客园

1、设计思想雷达扫描图&#xff0c;在影视作品中见到较多&#xff0c;比如飞机雷达、舰艇雷达&#xff0c;有一个扫描线转圈代表雷达一周旋转或一个批次的收发&#xff0c;发现目标就在表盘上标记位置。和汽车仪表盘类似&#xff0c;汽车仪表盘有底盘背景图、同圆、刻度、刻度值…

mysql降序后去重_Mysql 数据记录去重后按字段排序

实现效果&#xff1a;去重—取最新的—排序例子 : 按用户ID获取历史记录中某个人的记录&#xff0c;要求非重复的且每条只获取最新的&#xff0c;同时按添加时间倒序排列的实现 &#xff1a;SELECT *FROM (SELECT *FROM historysWHERE types_id1ORDER BY created DESC) AS BGRO…

linux php 守护进程,PHP程序员玩转Linux系列 使用supervisor实现守护进程

PHP程序员玩转Linux系列文章&#xff1a;首先遇到的问题是,部署nodejs的博客程序时,我把执行nodejs的命令放到后台,使用加&和nohup命令如:nodejs index.js & 或者 nohup nodejs index.js &&这个使用是当退出此次终端会话的时候就会停止, nohup这个命令理论上是…

计算机初级包括php吗,计算机的基本组成包括什么

计算机的基本组成包括控制器、运算器、存储器、输入设备和输出设备。其中&#xff0c;控制器是整个计算机的中枢神经&#xff0c;它的功能是对程序规定的控制信息进行解释&#xff0c;根据其要求进行控制&#xff0c;调度程序、数据&#xff0c;协调计算机各部分工作及内存与外…

php 获取今天数据,ThinkPHP 按日期获取今天获取本周获取本月获取今年数据

ThinkPHP 按日期或指定时间段获取今天、获取本周、获取本月、获取今年等数据。Db(gh_user)->whereTime(addTime, >, 2020-10-1)->select(); // 大于某个时间Db(gh_user)->whereTime(addTime, select(); // 小于某个时间Db(gh_user)->whereTime(addTime, between…

PHP复杂度,php 常用算法和时间复杂度

按数量级递增排列&#xff0c;常见的时间复杂度有&#xff1a;常数阶O(1),对数阶O(log2n),线性阶O(n),线性对数阶O(nlog2n),平方阶O(n2)&#xff0c;立方阶O(n3)复制代码 代码如下://二分查找O(log2n)function erfen($a,$l,$h,$f){if($l >$h){ return false;}$m intval(($l…

ubuntu11.10 源码编译安装php5.3.8,Ubuntu 11.10编译安装Nginx、PHP 5.3.8、MySQL、MongoDB、Memcached、SSL、SMTP...

手动安装php mongo扩展sudo apt-get install autoconfwget http://pecl.php.net/get/mongo-1.2.6.tgztar -zxvf mongo-1.2.6.tgzcd mongo-1.2.6/usr/local/php/bin/phpize./configure --with-php-config/usr/local/php/bin/php-configmakesudo make installecho extension mon…