5.Lock接口及其实现ReentrantLock

jdk1.7.0_79

  在java.util.concurrent.locks这个包中定义了和synchronized不一样的锁,重入锁——ReentrantLock,读写锁——ReadWriteLock等。在已经有了内置锁synchronized的情况下,为什么又出现了Lock显示锁呢?本文将以Lock作为Java并发包源码解读的开始.

  Lock定义最基本的加锁和解锁操作。

  

Lock 

void lock(); 

阻塞方式获取锁,直到获取锁后才返回 

void locklnterruptibly(); 

获取锁,除非当前线程被中断 

Condition newCondition(); 

返回一个Condition实例绑定到这个锁实例 

boolean tryLock(); 

不管是否获取到锁,都立即返回,非阻塞 

boolean tryLock(long time, TimeUnit unit); 

在一定时间内阻塞获取锁 

void unlock(); 

释放锁 

  Lock接口有一个实现类——重入锁ReentrantLock进入ReentrantLock类中我们就发现它对于Lock接口的实现基本上都借助于一个抽象静态内部类Sync,该内部类继承自AbstractQueuedSynchronizer,接着又发现两个静态内部类NonfairSyncFairSync,这两个静态内部类又是继承自刚刚的Sync这里就要引入两个新的概念了——公平锁与非公平锁。在公平的锁上线程将按照它们发出请求的顺序来获得锁但在非公平的锁上则允许“插队”:当一个线程请求非公平的锁时,如果在发出请求的同时该锁的状态变为可用,那么这个线程将跳过队列中所有的等待线程并获得这个锁。(《Java并发编程实战》)

  ReentrantLock你可以称之为重入锁(递归锁)、显示锁、排他锁(独占锁),显示锁很好理解即线程在获取锁和释放锁的时候都需要代码显示操作。重入锁是什么概念呢?synchronized实际上也是可重入锁,意思就是一个线程已经持有这个锁,当这个线程再次获得这个锁的时候不会被阻塞,而是使其同步状态计数器1,这样做的目的当然就是为了防止死锁,当线程释放这个锁的时候,同步状态计数器-1,直到递减至0才表示这个锁完全释放完毕,其他线程可以获取。那什么又是排他锁呢?我们直到AQS定义了两种模式下获取锁与释放锁的操作,那就是独占模式和共享模式,所谓独占模式就是只有一个线程能持有这个锁,而共享模式则是这个锁可以由多个线程所持有。例如ReebtrabtReadWriteLock的读锁就能由多个线程所持有。在知道了ReentrantLock的特性之后,我们再来看它其内部实现。 

  在前两节解析AQS的时候我们就提到,AQS所提供的同步器是实现锁的基础框架,固然ReentrantLock同样也是基于AQS,而ReentrantLock并没有直接实现AQS抽象类,而是将在其内部定义一个Sync内部类来聚合AQS,这样聚合而不是继承的目的是为了将锁的具体实现与锁的使用做一个隔离,锁的使用者关心的是锁如何才能被正确使用,而锁的实现者关心的是锁如何基于AQS被正确的实现。先讨论ReentrantLock$Sync抽象内部类在讨论前先回顾一下能够被子类重写的AQS方法有哪些: 

  

AbstractQueuedSynchronizer 

protected boolean tryAcquire(int arg) 

子类可实现在独占模式下获取同步状态的具体方法。 

protected boolean tryRelease(int arg) 

子类可实现在独占模式下释放同步状态的具体方法。 

protected int tryAcquireShared(int arg) 

子类可实现在共享模式下获取同步状态的具体方法。 

protected int tryReleaseShared() 

子类可实现在共享模式下释放同步状态的具体方法。 

protected boolean isHeldExclusively() 

当前同步器是否在独占模式下被线程占用,一般表示该方法是否被当前线程所独占。 

  通过查看ReentrantLock$Sync的源码可知,Sync一共对AQS重写了这么几个方法: 

  protected final boolean tryRelease(int release) protected final boolean isHeldExclusively() 

  为什么Sync只重写了这两个方法呢?实际上在ReentrantLock的内部还有另外两个内部类NonfairSync非公平锁和FairSync公平锁,这两个内部类是Sync的具体实现,很显然能够得出,对于锁的获取非公平锁和公平锁的实现是不一样的,而对于锁的释放两者均是相同实现。针对ReentrantLock的非公平锁和公平锁接下来我们来一一探讨他们的不同点和相同点。 

  ReentrantLock定义了一个成员变量——sync,并且他提供了两个构造方法,其默认无参构造方法创建的是非公平锁,而有参的构造方法则传入一个boolean类型来决定构造一个公平锁还是非公平锁。 

public class ReentrantLock implements Lock { private final Sync sync; public ReentrantLock() { sync = new NofairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } …… 
} 

  1.lock()

  针对开篇提到的Lock接口定义的方法,我们先来看ReentrantLockLock#lock的实现: 

public class ReentrantLock implements Lock { …… public void lock() { sync.lock(); }   …… 
}

  这个方法是抽象内部类定义的一个抽象方法,从命名可以看到这个类实际上就是AQSacquire获取锁的具体实现,在这里我们能看到非公平锁和公平锁对获取锁的不同实现,我们先来看非公平锁对Sync#lock的实现: 

static final class NonfairSync extends Sync { final void lock() { if (compareAndSetState(0, 1))  setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } protected final boolean tryAcquire(int acquire) {//在ReentrantLock$NonFairLock才终于看到了对AbstractQueuedSynchronizer#tryAcquire的具体实现。 return nonfairTryAcquire(acquires);//而tryAcquire的实现实际上又是在其父类ReentrantLock$Lock中实现的,好像有点绕,一会子类实现,一会父类实现。可以先这么来理解,既然它把tryAcquire的具体实现又定义在了父类,那说明这一定是父类对公共方法的抽取(Extract Method),其他地方一定有用到nonfairTryAcquire方法,不然JDK的作者不会闲的蛋疼。 } 
} 

  ReentrantLock$Sync中非公平锁的获取锁的实现 

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; 
} 

  我们说回ReentrantLock$NonFairLock非公平锁的lock阻塞获取锁的实现,在调用非公平锁的lock方法时,就会首先进行“抢锁”操作,也就是compareAndSetState这个方法是利用的CAS底层方法看能否抢占到锁,而不是按照先后获取获取锁的方式放到同步队列中获取锁,公平锁就是这样。既然说到了公平锁获取锁的方式,我们不妨和ReentrantLock$FairLock作一个对比: 

static final class FairSync extends Sync { inal void lock() { acquire(1); } protected final boolean tryAcquire(int acquires) {//在ReentrantLock$NonFairLock我们看到它老老实实的实现了AQS定义的tryAcquire方法,而没有调用父类的方法,从这里我们也基本能推断在ReentrantLock中没有其他地方会引用到这个方法。 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; } 
} 

  从公平锁(nonfairTryAcquire)公平锁(tryAcquire)的两个方法对比可知:公平锁在获取锁的时候首先会判断当前线程是否有前驱节点已经在队列中等待,如果有返回true,则不进行获取锁的操作,这一点就是和公平锁最大的区别,只有当前线程没有前驱节点才获取锁。ReentrantLock默认构造非公平锁,而实际上用得最多的也是非公平锁,公平锁从一定程度上能防止“饥饿”,但非公平锁在性能上却优于公平锁,我们做以下试验得知的确如此(详细试验过程《【试验局】ReentrantLock中非公平锁与公平锁的性能测试》) 

  2.lockInterruptibly()

这个方法和lock方法的区别就是,lock会一直阻塞下去直到获取到锁,而lockInterruptibly则不一样,它可以响应中断而停止阻塞返回。ReentrantLock对其的实现是调用的Sync的父类AbstractQueuedSynchronizer#acquireInterruptibly方法: 

//ReentrantLock#lockInterruptibly 
public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1);//因为ReentrantLock是排它锁,故调用AQS的acquireInterruptibly方法 
} 
//AbstractQueuedSynchronizer#acquireInterruptibly public final void acquireInterruptibly(int arg) throws InterruptedException{ if (Thread.interrupted()) //线程是否被中断,中断则抛出中断异常,并停止阻塞 throw new InterruptedException; if (!tryAcquire(arg)) //首先还是获取锁,具体参照上文 doAcquireInterruptibly(arg);//独占模式下中断获取同步状态 
} 

  通过查看doAcquireInterruptibly的方法实现不难发现它和acquireQueued大同小异,前者抛出异常,后者返回boolean。具体实在不再讨论,参照源码以及《2.从AbstractQueuedSynchronizer(AQS)说起(1)——独占模式的锁获取与释放》。 

  3.tryLock() 

  此方法为非阻塞式的获取锁,不管有没有获取锁都返回一个boolean值。 

//ReentrantLock#tryLock 
public boolean tryLock() { return sync.nonfairTryAcquire(1); 
} 

  可以看到它实际调用了Sync#nonfairTryAcquire非公平锁获取锁的方法,这个方法我们在上文lock()方法非公平锁获取锁的时候有提到,而且还特地强调了该方法不是在NonfairSync实现,而是在Sync中实现很有可能这个方法是一个公共方法,果然在非阻塞获取锁的时候调用的是此方法。详细解析参照上文。 

  4.tryLock(long timeout, TimeUnit unit) 

此方法是表示在超时时间内获取到同步状态则返回true,获取不到则返回false。由此可以联想到AQStryAcquireNanos(int arg, long nanosTimeOut)方法 

//ReentrantLock#tryLock 
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); 
} 

  果然Sync实际上调用了父类AQStryAcquireNanos方法。 

//AbstractQueuedSynchronizer#tryAcquireNanos public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { if (Thread.interrupted())  throw new InterruptedException();//可以看到前面和lockInterruptibly一样 return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout);//首先也会先尝试获取锁 
} 

  在doAcquireNanos实际也和acquireQueueddoAcquireInterruptibly差不多,不同的是增加了超时判断。 

  关于LockReentrantLock介绍到这里,在AQS和这里遗留了一个问题——Condition,在下一节中单独介绍Condition 

转载于:https://www.cnblogs.com/yulinfeng/p/6906597.html

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

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

相关文章

oracle近三个月内,在oracle中的前三个月SQL

我有以下的sql&#xff0c;它给了我最后一整个三个月的人处理的档案报告&#xff0c;但我想采用它&#xff0c;以便从前三个月获得这些&#xff0c;例如&#xff0c;当我运行现在我应该从2011年10月11日和12日以及2012年1月2日和3日的4月份获得档案。有人可以提出一个建议&…

什么是递归

概念&#xff1a; 递归指的是方法定义中调用方法本身的现象&#xff08;自己调自己&#xff09;把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算 递归注意事项&#xff1a; 递归一定要有出口。…

错误问题

1、系统出现如下错误&#xff1a;warning: LF will be replaced by CRLF 原因分析&#xff1a; CRLF -- Carriage-Return Line-Feed 回车换行 就是回车(CR, ASCII 13, \r) 换行(LF, ASCII 10, \n)。 这两个ACSII字符不会在屏幕有任何输出&#xff0c;但在Windows中广泛使用来标…

oracle监听为blocked,关于“Instance +ASM1, status BLOCKED, ”

确实是可以的~只是需要配置一下~不过这个如果不操作ASM instacne的话没什么用Applies to:Oracle Net Services - Version: 10.1.0.4.0 to 10.2.0.4.0Information in this document applies to any platform.SymptomsChecking the listener status, ASM instance is shown as BL…

彻底学会IO流

概述&#xff1a; IO流就是用来处理设备间数据传输问题的.常见的应用: 文件复制; 文件上传; 文件下载IO的数据传输&#xff0c;可以看做是一种数据的流动&#xff0c;按照流动的方向&#xff0c;已内存为参照物&#xff0c;进行读写操作IO可以保存到文件&#xff0c;其实就是内…

一些芯片资料

74hc14d u10 u11 u12 六反相触发器 74HC244 三态八缓冲器 u15 u13 uln2003afwg u16 74HC07 u17 L298N 步进电机驱动芯片 MOS管认知 转载于:https://www.cnblogs.com/legion/p/6908434.html

debian php安装pdo扩展,docker安装PHP扩展2020-05-25

示例&#xff1a;1、docker-php-ext-install pdo_mysql2、extensionphp_pdo_mysql.dll一、docker按照PHP扩展先删除原来的composer容器&#xff0c;重新执行以下命令&#xff1a;docker run -it --name composer -v E:\docker\nginx\www\YYXTServer:/app --privilegedtrue comp…

DP Intro - Tree DP Examples

因为上次比赛sb地把一道树形dp当费用流做了&#xff0c;受了点刺激&#xff0c;用一天时间稍微搞一下树形DP&#xff0c;今后再好好搞一下&#xff09; 基于背包原理的树形DP poj 1947 Rebuilding Roads 题意&#xff1a;给你一棵树,让你求最少剪掉多少条边可以剪出一棵点数为m…

强大的缓冲流

概述&#xff1a; 缓冲流的概述BufferedInputStream: 字节输入缓冲流BufferedOutputStream: 字节输出缓冲流四种方式复制字符缓冲流文本排序字符流读字符乱码问题InputStreamReader转换流读取字符数据OutputStreamWriter转换流写字符数据转换文件编码 1.缓冲流的概述&#xf…

linux驱动头文件查找目录,在Fedora 20中查找简单设备驱动程序的头文件

因此&#xff0c;我尝试按照简单的示例加载ORielly Linux设备驱动程序手册中的“ Hello World”设备驱动程序。问题是&#xff0c;由于某种原因&#xff0c;除非我在include语句中显式定义头文件的路径&#xff0c;否则它将无法正常工作。即我必须输入#include 而不只是#includ…

Feel Good

传送门 Time Limit: 3000MS Memory Limit: 65536KTotal Submissions: 14435 Accepted: 3996Case Time Limit: 1000MS Special JudgeDescription Bill is developing a new mathematical theory for human emotions. His recent investigations are dedicated to studying how g…

转换流/序列化/反序列化

转换流&#xff1a; 使用转换流可以在一定程度上避免乱码&#xff0c;还可以指定输入输出所使用的字符集 InputStreamReader&#xff1a;是从字节流到字符流的桥梁&#xff0c;父类是Reader OutputStreamWriter&#xff1a;是从字符流到字节流的桥梁&#xff0c;父类是Writer 转…

linux进程map,LInux环境运行mapReduce程序

将工程整体打成一个jar包并上传到linux机器上&#xff0c;准备好要处理的数据文件放到hdfs的指定目录中用命令启动jar包中的Jobsubmitter&#xff0c;让它去提交jar包给yarn来运行其中的mapreduce程序 &#xff1a;hadoop jar wc.jar cn.edu360.mr.wordcount.JobSubmitter ...…

python+unittest框架整理(一点点学习前辈们的封装思路,一点点成长。。。)

预期框架整理目标&#xff1a; 1.单个用例维护在单个.py文件中可单个执行&#xff0c;也可批量生成组件批量执行 2.对定位参数&#xff0c;定位方法&#xff0c;业务功能脚本&#xff0c;用例脚本&#xff0c;用例批量执行脚本&#xff0c;常用常量进行分层独立&#xff0c;各自…

linux 不知道root密码怎么办,linux下忘记root密码怎么办

在linux系统操作中&#xff0c;为了系统的安全&#xff0c;会给系统中的root账户设置密码&#xff0c;那么忘记密码是时常发生的事情。如果忘记了root的密码该怎么办呢?下面秋天网 Qiutian.ZqNF.Com小编就给大家介绍下linux下忘记root密码的解决方法。linux是一套免费使用和自…

scrapy爬个小网站

本文使用scrapy对某一个网站静态数据进行了抓取# -*- coding: utf-8 -*- import scrapy from scrapy.http import request import requests import os import sys reload(sys) sys.setdefaultencoding(utf-8)#中文字符不能被识别报错 class spider(scrapy.Spider):namepicSpi…

linux如何标识用户账号和组账号,linux管理用户和组

唯一标识&#xff1a; UID GID (管理员root的UID为0)组的分类&#xff1a; 基本组 附加组(从属组)基本组&#xff1a;Linux自己创建的组&#xff0c;与用户同名&#xff0c;系统自动将用户加入附加组(从属组)&#xff1a;管理员自建创建&#xff0c;管理员将用户加入Linux一个…

Windows 相关链接

c运行库、c标准库、windows API的区别和联系 http://www.cnblogs.com/renyuan/p/5031100.html 转载于:https://www.cnblogs.com/jidongdeatao/p/6916260.html

vs远程编译linux程序,使用Visual Studio 2015远程调试Linux程序

##安装 Visual Studio 2015安装时注意将跨平台移动开发->Visual C移动开发->Viaual C Android 开发的选项勾上##安装PUTTYVisual Studio依赖putty中的plink来连接Linux机器并发送命令##使用首先在Visual Studio中新建一个空项目这里是列表文本接下来将代码导入到这个空项…

都在说反射,反射到底是什么

概念&#xff1a; 什么是反射? 利用反射可以无视修饰符获取类里面所有的属性和方法对于任何对象&#xff0c;都能够调用它的方法和属性&#xff0c;这种动态获取信息以及动态调用对象方法的功能称为Java的反射 反射的应用场景? 常见的有&#xff1a; idea的智能提示、框架等…