unit类型是什么?_面试官虚晃一枪:项目中有用过锁吗?能解释一下什么是AQS?...

1e932105379ae1cdaac2cf596d582247.png

1 前言

锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能防止多个线程同时访问共享资源(但是有些锁可以允许多个线程并发的访问共享资源,如读写锁)。在以前,Java程序是靠synchronized来实现锁功能的,而在Java SE 5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,他提供了与synchronized关键字类似的同步功能,只是在使用时需要显式的获取锁和释放锁,虽然它缺少了synchronized提供的隐式获取释放锁的便捷性,但是却拥有了锁获取和释放的可操作性、可中断的获取锁以及超时获取锁等多种synchronized关键字不具备的同步特性。很多锁都通过实现Lock接口来完成对锁的操作,比如可重入锁(ReentrantLock)、前一张讲的Redisson分布式锁等,而Lock接口的实现,基本是都是通过聚合了一个同步器的子类来完成线程访问控制的,而同步器,就是我们常说的AQS(AbstractQueuedSynchronizer),也是今天要记录的内容。

2 什么是AQS

AQS(队列同步器AbstractQueuedSynchronizer)是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。

b4b3bc382a41d30ac1b0757376044927.png

如上图所示,同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,在抽象方法的实现过程中免不了要对同步状态进行修改,这时就需要使用同步器提供的3个方法来进行操作:

1、getState():获取当前同步状态

e76c0ed622fdc7f60f6d324125bfd99e.png

2、setState():设置当前同步状态

7678747593d2bf9c8548279d43d032b2.png

3、compareAndSetState(int expect, int update):通过CAS设置当前状态,该方法能保证状态设置的原子性

325b9546f6ec36da8f1e89d1a0731ec2.png

子类推荐被定义为自定义同步组件的静态内部类,同步器自身没有实现任何同步接口,它仅仅是定义了若干同步状态获取和释放的方法来供自定义同步组件使用。同步器既可以支持独占式获取同步状态(单个线程获取锁),也可以支持共享式获取同步状态(多个线程获取到锁),这样就可以方便实现不同类型的同步组件(如上图所示的可重入锁:ReentrantLock、可重入读写锁:ReentrantReadWriteLock、计数器:CountDownLatch等等)

3 同步器可重写的方法

以下代码为可重入锁继承同步器后重写的方法:

  • protected boolean tryAcquire(int acquires):独占式获取同步锁状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后再进行CAS设置同步状态。
/*** 非公平锁*/static final class NonfairSync extends Sync {private static final long serialVersionUID = 7316153563782823691L;/*** Performs lock.  Try immediate barge, backing up to normal* acquire on failure.*/final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}protected final boolean tryAcquire(int acquires) {// nonfairTryAcquire方法和下面的公平锁方法除了判断是否在队列首位之外没有不同return nonfairTryAcquire(acquires);}}/*** 公平锁*/static final class FairSync extends Sync {private static final long serialVersionUID = -3000897897090466540L;final void lock() {acquire(1);}/*** Fair version of tryAcquire.  Don't grant access unless* recursive call or no waiters or is first.*/protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();// 获取当前同步状态int c = getState();// 判断是否符合预期if (c == 0) {// 判断是否在队列首位并且CAS设置当前状态成功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;}}
  • protected boolean tryRelease(int acquires):独占式释放同步锁状态,等待获取同步状态的线程将有机会获取同步状态。
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;}
  • protected boolean isHeldExclusively():当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程锁独占。
protected final boolean isHeldExclusively() {// While we must in general read state before owner,// we don't need to do so to check if current thread is ownerreturn getExclusiveOwnerThread() == Thread.currentThread();}

以下代码为读写锁继承同步器后重写的方法:

  • protected int tryAcquireShared(int arg):共享式获取同步状态,返回大于0的值,表示获取锁成功,反之获取锁失败。
protected final int tryAcquireShared(int unused) {Thread current = Thread.currentThread();// 获取当前同步状态int c = getState();// 如果有线程持有写锁,则返回-1if (exclusiveCount(c) != 0 &&getExclusiveOwnerThread() != current)return -1;// 获取持有锁的线程数int r = sharedCount(c);// 判断是否阻塞,判断线程数量,判断CAS设置是否成功if (!readerShouldBlock() &&r < MAX_COUNT &&compareAndSetState(c, c + SHARED_UNIT)) {// 当前线程是第一个获取到锁的线程if (r == 0) {firstReader = current;firstReaderHoldCount = 1;// 否则就++} else if (firstReader == current) {firstReaderHoldCount++;} else {  HoldCounter rh = cachedHoldCounter;if (rh == null || rh.tid != getThreadId(current))cachedHoldCounter = rh = readHolds.get();else if (rh.count == 0)readHolds.set(rh);rh.count++;}return 1;}// 死循环去获取锁return fullTryAcquireShared(current);}
  • protected boolean tryReleaseShared(int arg):共享式释放同步状态。
protected final boolean tryReleaseShared(int unused) {Thread current = Thread.currentThread();// 对应上面获取锁来读就好了if (firstReader == current) {// assert firstReaderHoldCount > 0;if (firstReaderHoldCount == 1)firstReader = null;elsefirstReaderHoldCount--;} else {HoldCounter rh = cachedHoldCounter;if (rh == null || rh.tid != getThreadId(current))rh = readHolds.get();int count = rh.count;if (count <= 1) {readHolds.remove();if (count <= 0)throw unmatchedUnlockException();}--rh.count;}for (;;) {int c = getState();int nextc = c - SHARED_UNIT;if (compareAndSetState(c, nextc))// Releasing the read lock has no effect on readers,// but it may allow waiting writers to proceed if// both read and write locks are now free.return nextc == 0;}}

上面例子,因为读写锁是共享锁,可重入锁是独占锁,而同步器对于共享锁和独占锁都提供了可重写的方法来获取锁或者释放锁,所以分了两个例子来写。

4 同步器提供的模版方法

  • void acquire(int arg):独占式获取同步状态,如果当前线程获取同步状态成功则返回,否则,将会进入同步队列等待。
public final void acquire(int arg) {// 如果获取锁失败,则加入同步队列,如果加入同步队列成功则自旋阻塞唤醒来不断的尝试获取锁,直到线程被中断或获取到锁if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}
  • void acquireInterruptibly(int arg):与上面acquire相似,但是当前方法如果在获取锁的过程中线程中断会抛出InterruptedException并返回。
public final void acquireInterruptibly(int arg) throws InterruptedException {// 线程中断抛出异常if (Thread.interrupted())throw new InterruptedException();// 如果没有获取到锁if (!tryAcquire(arg))// 不断自旋尝试获取锁doAcquireInterruptibly(arg);}
  • boolean tryAcquireNanos(int arg, long nanosTimeout):在acquireInterruptibly()方法的基础上增加了超时时间,如果在超时时间内获取到了锁,则返回true,否则返回false。
public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {// 中断抛异常if (Thread.interrupted())throw new InterruptedException();// 相应时间内不断获取锁,超时返回falsereturn tryAcquire(arg) ||doAcquireNanos(arg, nanosTimeout);}
  • void acquireShared(int arg):共享式的获取同步状态,如果当前线程未获取到同步状态,则会进入同步队列等待,与独占锁的主要区别是在同一时刻可以有多少个线程获取到同步状态。
public final void acquireShared(int arg) {// 如果获取锁失败,则不断自旋尝试获取锁,tryAcquireShared方法在上面有讲if (tryAcquireShared(arg) < 0)doAcquireShared(arg);}
  • void acquireSharedInterruptibly(int arg):与acquireShared方法相似,只是如果线程中断,当前方法会抛出异常。
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {// 中断抛出异常if (Thread.interrupted())throw new InterruptedException();// 如果获取锁失败,则不断自旋尝试获取锁if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);}
  • boolean tryAcquireSharedNanos(int arg, long nanosTimeout):在acquireSharedInterruptibly方法的基础上增加了超时时间。
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException {// 中断抛出异常if (Thread.interrupted())throw new InterruptedException();// 在超时时间内如果获取锁失败,则不断自旋尝试获取锁return tryAcquireShared(arg) >= 0 ||doAcquireSharedNanos(arg, nanosTimeout);}
  • boolean release(int arg):独占式释放同步状态,该方法在释放同步状态之后,会将队列中的第一个节点包含的线程唤醒。
public final boolean release(int arg) {// 如果获取锁成功if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)// 唤醒第一个节点的线程unparkSuccessor(h);return true;}return false;}
  • boolean releaseShared(int arg):共享式释放同步状态。
public final boolean releaseShared(int arg) {// 如果释放锁成功if (tryReleaseShared(arg)) {// 唤醒线程doReleaseShared();return true;}return false;}
  • Collection getQueuedThreads():获取等待在同步队列上的线程集合。
public final Collection<Thread> getQueuedThreads() {ArrayList<Thread> list = new ArrayList<Thread>();for (Node p = tail; p != null; p = p.prev) {Thread t = p.thread;if (t != null)list.add(t);}return list;}

同步器提供的模板方法基本是分为3类:

  • 独占式获取与释放同步状态
  • 共享式获取与释放同步状态
  • 查询同步队列中的等待线程集合

5 根据同步器自定义同步组件

上面介绍了一些AQS提供的可重写方法和模板方法,接下来我们自定义一个独占锁(在同一时刻只有一个线程能获取锁,其他获取锁的线程只能处于同步队列中,当获取到锁的线程释放锁之后,后面的线程才能够获取锁)

public class ExclusiveLock implements Lock {/*** 自定义同步器*/private static class Sync extends AbstractQueuedSynchronizer{// 判断是否处于占用状态@Overrideprotected boolean isHeldExclusively(){return getState() == 1;}// 加锁@Overrideprotected boolean tryAcquire(int arg) {// 通过CAS设置同步状态(设置成功返回true 设置失败返回false)if (compareAndSetState(0, 1)){setExclusiveOwnerThread(Thread.currentThread());return true;}return false;}// 释放锁@SneakyThrows@Overrideprotected boolean tryRelease(int arg) {// 如果同步状态为未获取锁,则抛出异常,没有线程获取到锁,不能释放锁if (getState() == 0){throw new IllegalAccessException();}// 释放锁setExclusiveOwnerThread(null);setState(0);return true;}// 返回一个Condition,每个Condition都包含一个Condition队列protected Condition newCondition() {return new ConditionObject();}}private final Sync sync = new Sync();@Overridepublic void lock() {// 独占式加锁sync.acquire(1);}@Overridepublic void lockInterruptibly() throws InterruptedException {// 加锁线程中断抛出异常,否则自旋加锁sync.acquireInterruptibly(1);}@Overridepublic boolean tryLock() {// 加锁成功返回true,否则设置占用排它锁的线程是当前线程return sync.tryAcquire(1);}@Overridepublic boolean tryLock(long time, TimeUnit unit) throws InterruptedException {// 加锁线程中断抛出异常,否则在有效时间内尝试自旋加锁return sync.tryAcquireSharedNanos(1, unit.toNanos(time));}@Overridepublic void unlock() {// 释放锁sync.release(1);}@Overridepublic Condition newCondition() {// 返回Conditionreturn sync.newCondition();}
}

如上代码,我们自定义了一个独占锁,它在同一时刻只允许一个线程占有锁。sync内部类继承了同步器并实现了独占式获取和释放同步状态。在tryAcquire(int arg)方法中,如果通过CAS设置成功,则代表获取了同步状态,而在tryRelease(int arg)方法中只是将同步状态重制为0。用户在使用ExclusiveLock时并不会直接和内部同步器打交道,而是调用ExclusiveLock提供的方法即可,如加锁调用lock()方法,如果获取锁失败则会被加入同步队列中,释放锁调用unlock()方法,如果没有线程获取锁的时候释放锁会抛出异常,还可以按指定时间尝试获取锁等等。

结尾

本来想把同步器实现原理也写一些的,结果看了一下篇幅好想有些许长,那就分两篇来写把,如果看完感觉有帮助的,请帮忙点个赞,谢谢各位,有缘下篇文章再见!

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

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

相关文章

浏览器里面看到的表单数据映射到python_python爬虫入门01:教你在 Chrome 浏览器轻松抓包...

通过python爬虫入门&#xff1a;什么是爬虫&#xff0c;怎么玩爬虫&#xff1f;我们知道了什么是爬虫也知道了爬虫的具体流程那么在我们要对某个网站进行爬取的时候要对其数据进行分析就要知道应该怎么请求就要知道获取的数据是什么样的所以我们要学会怎么抓咪咪&#xff01;哦…

c#二叉树 取叶子节点个数_两种类似但是原理不同的算法求二叉树的所有叶子节点和...

技术提高是一个循序渐进的过程&#xff0c;所以我讲的leetcode算法题从最简单的level开始写的&#xff0c;然后到中级难度&#xff0c;最后到hard难度全部完。目前我选择C语言&#xff0c;Python和Java作为实现语言&#xff0c;因为这三种语言还是比较典型的。由于篇幅和精力有…

所有的service报红但不报错_从一个应用报错来看centos系统的/tmp目录自动清理规则...

概述分享最近应用碰到的一个奇怪bug&#xff0c;一开始以为是代码上的问题&#xff0c;找了一段时间发现居然是因为系统的一个自动清理规则导致&#xff0c;下面一起来看看吧~一、应用报错&#xff1a;logwire.core.exceptions.GeneralUnhandledException: 服务端未处理异常...…

springboot中接口实例化_AngularJs中控制器的定义,实例化,作用域范围

AngularJs中控制器的定义&#xff0c;实例化&#xff0c;作用域范围基于AngularJS入门与进阶(江荣波 著)这本书的笔记AngularJS 1.x的demoAngularJS1.x和Angular2,4,5是不一样的两个东西&#xff0c;构建方式&#xff0c;语法&#xff0c;都很多不同AngularJs控制器定义与实例化…

高斯核函数参数确定_高斯过程

之前看过高斯过程(GP)&#xff0c;不过当时也没太看懂&#xff0c;最近花时间认真研究了一下&#xff0c;感觉总算是明白咋回事了&#xff0c;本文基于回归问题解释GP模型的思想和方法。文中的想法是自己思考总结得来&#xff0c;并不一定准确&#xff0c;也可能存在错误性。为…

uniapp光标自动定义到文本框_特检自动化行吊静力检测方案

主要测量功能使用徕卡测量开发的Windows版数据传输软件&#xff0c;通过蓝牙连接徕卡DISTO&#xff0c;经过简单的测量周期设置&#xff0c;即可实现自动化的距离检测。测量数据还可以输出Excel&#xff0c;甚至可以实时发送至PC运行的第三方软件中&#xff0c;这么强大的软件还…

安装python时需要勾选_一体化污水处理设备安装时需要注意事项

一体化污水处理设备用于处理生活污水和低浓度有机污水&#xff0c;它基本上采用机电完全封闭的结构&#xff0c;不需要专业人员进行管理。它方便且易于清洁&#xff0c;因此引起了很多关注。一体化污水处理设备的安装方法通常为三种&#xff1a;地埋式&#xff0c;地上式和半地…

oracle驱动maven报错_在Maven仓库中添加Oracle JDBC驱动

由于Oracle授权问题&#xff0c;Maven3不提供Oracle JDBC driver&#xff0c;为了在Maven项目中应用Oracle JDBC driver,必须手动添加到本地仓库。一.首先要得到Oracle JDBC Driver2.通过Oracle的安装目录获得&#xff0c;位置在“{ORACLE_HOME}jdbclibojdbc14.jar”二.手动安装…

python3.6程序_python3.6如何生成exe程序

PyInstaller的原理简介PyInstaller其实就是把python解析器和你自己的脚本打包成一个可执行的文件&#xff0c;和编译成真正的机器码完全是两回事&#xff0c;所以千万不要指望成打包成一个可执行文件会提高运行效率&#xff0c;相反可能会降低运行效率&#xff0c;好处就是在运…

java切片_ java中一个极其强悍的新特性Stream详解(非常实用)

java8中有两个非常有名的改进&#xff0c;一个是Lambda表达式&#xff0c;一个是Stream。如果我们了解过函数式编程的话&#xff0c;都知道Stream真正把函数式编程的风格引入到了java中。这篇文章由简入繁逐步介绍Stream。一、Stream是什么从名字来看&#xff0c;Stream就是一个…

java获取网络图片_有了这50套Java毕设项目(源码 案例),offer拿到手软,无偿分享...

简介:又到了开学季&#xff0c;不少人都很是烦恼&#xff0c;手把手教你做Java毕设项目&#xff0c;有教程视频源码100套随意选择&#xff0c;试试手&#xff01;&#xff01;列举其中2个系统大纲在线考试系统1&#xff0e;综述网络考试系统的项目背景及国内外发展现状&#xf…

linux将日期和日历信息追加到文件中_Linux常用指令

常用指令 1、帮助指令 man [指令或者配置文件] help 指令 2、文件目录类指令 1、pwd 功能:显示当前工作目录的绝对路径 2、ls [选项] [目录或者文件] 功能:列出文件名和目录使用:ls -l 以列表的形式显示信息ls -a …

一个搜索框多个按钮_网站搜索栏设计指南:要不要?怎么设计?

在网站的设计过程中&#xff0c;搜索栏是一个很容易被忽略的部分&#xff0c;但用户却依赖它来寻找特定的信息。由于搜索栏是网站中最常用的元素之一&#xff0c;所以搜索栏的设计对用户体验有着重要的影响。网站是否需要搜索栏&#xff1f;搜索栏使用背后的思维和心理是帮助用…

状态机设计的一般步骤_浅谈状态机

来源&#xff1a;公众号【ZYNQ】ID &#xff1a;FreeZynq整理 &#xff1a;李肖遥本文目录前言状态机简介状态机分类Mealy 型状态机Moore 型状态机状态机描述一段式状态机二段式状态机三段式状态机状态机优缺点总结扩展-四段式状态机01. 前言状态机是FPGA设计中一种非常重要、…

java中default_Java 中关于default 访问权限的讨论

Java中关于成员变量访问权限问题一般书中会给出如下表格&#xff1a;简单地描述一下表中的内容&#xff1a;用private 修饰的成员变量只能在类内部访问&#xff1b;用default修饰的成员变量可以再内部访问&#xff0c;也可以被同个包(同一目录)中的类访问&#xff1b;default修…

java 输出当月日历_java 实现打印当前月份的日历

实现当前日历的打印&#xff0c;当前日期用*来表示。关键得出这个月的第一天是星期几。基姆拉尔森计算公式W (d2*m3*(m1)/5yy/4-y/100y/400) mod 7在公式中d表示日期中的日数1&#xff0c;m表示月份数。y表示年数。注意1&#xff1a;在公式中有个与其它公式不同的地方&#xff…

xmlhttprequest 跨域_跨域资源共享(CORS)安全性

跨域资源共享(CORS)安全性背景 提起浏览器的同源策略&#xff0c;大家都很熟悉。不同域的客户端脚本不能读写对方的资源。但是实践中有一些场景需要跨域的读写&#xff0c;所以出现了一些hack的方式来跨域。比如在同域内做一个代理&#xff0c;JSON-P等。但这些方式都存在缺陷&…

java 图片识别 tess4j_图像文字识别(四):java调用tess4j识别图像文字

转自&#xff1a;https://blog.csdn.net/a745233700/article/details/80203340javajava调用tess4j识别图像文字Tesseract-OCR支持中文识别&#xff0c;而且开源和提供全套的训练工具&#xff0c;是快速低成本开发的首选。前面记录过在java中调用tesseract-orc&#xff0c;该方法…

sql in转换为join_同一个SQL语句,为啥性能差异咋就这么大呢?(1分钟系列)

《数据库允许空值&#xff0c;往往是悲剧的开始》一文通过explain来分析SQL的执行计划&#xff0c;来分析null对索引命中情况的影响&#xff0c;有不少朋友留言&#xff0c;问explain结果中的type字段&#xff0c;ref&#xff0c;ALL等不一样的值究竟是什么含义。今天花1分钟简…

java字符串笔试题_五道Java常见笔试题及答案汇总

1、String和StringBuffer的区别&#xff1f;答&#xff1a;Java平台提供了两个类&#xff1a;String和StringBuffer&#xff0c;它们可以储存和操作字符串&#xff0c;即包含多个字符的字符数据。这个String类提供了数值不可改变的字符串。而这个StringBuffer类提供的字符串进行…