ReentrantLock是一个可重入的互斥锁,ReentrantLock由最近成功获取锁,还没有释放的线程所拥有,当锁被另一个线程拥有时,调用lock的线程可以成功获取锁。如果锁已经被当前线程拥有,当前线程会立即返回。此类的构造方法提供一个可选的公平参数

  1. public ReentrantLock(boolean fair) {
  2. sync = fair ? new FairSync() : new NonfairSync();
  3. }

公平与非公平有何区别,所谓公平就是严格按照FIFO顺序获取锁,非公平安全由程序员自己设计,比如可以按优先级,也可以按运行次数等规则来选择。

在AQS里面有一个state字段,在ReentrantLock中表示锁被持有的次数,它是一个volatile类型的整型值,因此对它的修改可以保证其他线程可以看到。ReentrantLock顾名思义就是锁可以重入,一个线程持有锁,state=1,如果它再次调用lock方法,那么他将继续拥有这把锁,state=2.当前可重入锁要完全释放,调用了多少次lock方法,还得调用等量的unlock方法来完全释放锁。下面简单看一下它的获取与释放过程:

ReentrantLock实现了Lock接口,获取锁是通过lock方法来实现的,整个过程和AQS的获取过程一样,这里不再分析,只分析它的tryAcquire方法的实现。tryAcquire方法有公平版本与非公平版权,源于ReentrantLock使用了两种同步器,具体使用哪一个是在构造方法中提供的公平参数。先看看公平版本的tryAcquire方法:

  1. /**
  2. * Fair version of tryAcquire.  Don't grant access unless
  3. * recursive call or no waiters or is first.
  4. */
  5. protected final boolean tryAcquire(int acquires) {
  6. final Thread current = Thread.currentThread();
  7. int c = getState();
  8. if (c == 0) {
  9. if (!hasQueuedPredecessors() &&
  10. compareAndSetState(0, acquires)) {
  11. setExclusiveOwnerThread(current);
  12. return true;
  13. }
  14. }
  15. else if (current == getExclusiveOwnerThread()) {
  16. int nextc = c + acquires;
  17. if (nextc < 0)
  18. throw new Error("Maximum lock count exceeded");
  19. setState(nextc);
  20. return true;
  21. }
  22. return false;
  23. }

1、首先判断锁有没有被持有,如果被持有,就判断持有锁的线程是不是当前线程,如果不是就啥也不做,返回获取失败,如果是就增加重入数,返回成功获取;

2、如果锁没有被任何线程持有(c==0),首先判断当前结点前面是否还有线程在排除等待锁,如果有,直接返回获取失败,否则将锁持有数设为acquires,一般为1,然后设置锁的拥有者为当前线程,成功获取。

整个过程比较简单,再来看看非公平的tryAcquire方法:

  1. /**
  2. * Performs non-fair tryLock.  tryAcquire is
  3. * implemented in subclasses, but both need nonfair
  4. * try for trylock method.
  5. */
  6. final boolean nonfairTryAcquire(int acquires) {
  7. final Thread current = Thread.currentThread();
  8. int c = getState();
  9. if (c == 0) {
  10. if (compareAndSetState(0, acquires)) {
  11. setExclusiveOwnerThread(current);
  12. return true;
  13. }
  14. }
  15. else if (current == getExclusiveOwnerThread()) {
  16. int nextc = c + acquires;
  17. if (nextc < 0) // overflow
  18. throw new Error("Maximum lock count exceeded");
  19. setState(nextc);
  20. return true;
  21. }
  22. return false;
  23. }

代码几乎一模一样,唯一不同的是在知道锁持有数为0时,直接将当前线程设置为锁的持有者,这一点和公平版本的tryAcquire是有区别的,也就是说非公平机制采用的是抢占式模型。

看完了锁的获取,再来看锁的释放,锁的释放就不存在公平与非公平一说。

  1. protected final boolean tryRelease(int releases) {
  2. int c = getState() - releases;
  3. if (Thread.currentThread() != getExclusiveOwnerThread())
  4. throw new IllegalMonitorStateException();
  5. boolean free = false;
  6. if (c == 0) {
  7. free = true;
  8. setExclusiveOwnerThread(null);
  9. }
  10. setState(c);
  11. return free;
  12. }

1、首先判断当前线程是不是拥有锁的线程,如果不是,抛出IllegalMonitorStateException异常,这个异常表明表明某一线程已经试图等待对象的监视器,或者试图通知其他正在等待对象的监视器而本身没有指定监视器的线程;

2、判断释放之后的锁持有数c,如果c!=0,先设置state为该值,然后返回false表示没有被完全释放。如果c==0,表示锁被完全释放,当前线程释放对锁的拥有,返回true,表示已完全释放。

此外ReentrantLock还有一些其他方法,但大部分都是直接代理了AQS中的方法,顺便提一下,可以使用isHeldByCurrentThread() 和 getHoldCount()方法来检查当前线程是否拥有该锁

  1. final int getHoldCount() {
  2. return isHeldExclusively() ? getState() : 0;
  3. }
  4. public boolean isHeldByCurrentThread() {
  5. return sync.isHeldExclusively();
  6. }
  7. protected final boolean isHeldExclusively() {
  8. return getExclusiveOwnerThread() == Thread.currentThread();
  9. }

JDK文档还提到,可以将isHeldExclusively用于调试和测试,例如,只在保持某个锁时才应调用的方法可以声明如下:

  1. class X {
  2. ReentrantLock lock = new ReentrantLock();
  3. // ...
  4. public void m() {
  5. assert lock.isHeldByCurrentThread();
  6. // ... method body
  7. }
  8. }

还可以用此方法来确保某个重入锁是否以非重入方式使用的,例如:

  1. class X {
  2. ReentrantLock lock = new ReentrantLock();
  3. // ...
  4. public void m() {
  5. assert !lock.isHeldByCurrentThread();
  6. lock.lock();
  7. try {
  8. // ... method body
  9. } finally {
  10. lock.unlock();
  11. }
  12. }
  13. }

参考资料:

JDK文档:ReentrantLock

Java 理论与实践: JDK 5.0 中更灵活、更具可伸缩性的锁定机制

JAVA并发编程学习笔记之ReentrantLock的更多相关文章

  1. Java并发编程学习笔记

    Java编程思想,并发编程学习笔记. 一.基本的线程机制 1.定义任务:Runnable接口 线程可以驱动任务,因此需要一种描述任务的方式,这可以由Runnable接口来提供.要想定义任务,只需实现R ...

  2. Java并发编程学习笔记 深入理解volatile关键字的作用

    引言:以前只是看过介绍volatile的文章,对其的理解也只是停留在理论的层面上,由于最近在项目当中用到了关于并发方面的技术,所以下定决心深入研究一下java并发方面的知识.网上关于volatile的 ...

  3. Java 并发编程学习笔记 理解CLH队列锁算法

    CLH算法实现 CLH队列中的结点QNode中含有一个locked字段,该字段若为true表示该线程需要获取锁,且不释放锁,为false表示线程释放了锁.结点之间是通过隐形的链表相连,之所以叫隐形的链 ...

  4. Java并发编程学习笔记(一)——线程安全性

    主要概念:线程安全性.原子性.原子变量.原子操作.竟态条件.复合操作.加锁机制.重入.活跃性与性能. 1.当多个线程访问某个状态变量并且其中有一个线程执行写入操作时,必须采用同步机制来协同这些线程对变 ...

  5. [转]JAVA并发编程学习笔记之Unsafe类

    1.通过Unsafe类可以分配内存,可以释放内存:类中提供的3个本地方法allocateMemory.reallocateMemory.freeMemory分别用于分配内存,扩充内存和释放内存,与C语 ...

  6. JAVA并发编程学习笔记------多线程调优

    1. 多线程场景下尽量使用并发容器代替同步容器 (如ConcurrentHashMap代替同步且基于散列的Map, 遍历操作为主要操作的情况下用CopyOnWriteArrayList代替同步的Lis ...

  7. Java并发编程学习笔记(三)——对象的组合

    重要概念: 1.在设计线程安全类的过程中,需要包含以下三个基本要素: (1)找出构成对象状态的所有变量. (2)找出约束状态变量的不变性条件. (3)建立对象状态的并发访问管理策略. 2.

  8. java并发编程学习笔记(一)初识并发原子性

    1.并发的意义 现在是一个多核的时代,并发的存在意义就是为了能够充分利用多核计算机的优势,提高程序的运行效率: 2.并发的风险 竞争-----多个线程对内存数据数据进行读写操作时,对数据处理结果的一个 ...

  9. JAVA并发编程学习笔记------协作对象之间发生的死锁

    一. 如果在持有锁时调用某个外部方法,那么将出现活跃性问题.在这个外部方法中可能会获取其他锁(这可能会产生死锁),或者阻塞时间过长,导致其他线程无法及时获得当前被持有的锁.如下代码: public c ...

随机推荐

  1. Python数据结构————二叉查找树的实现

    对于二叉查找树的每个节点Node,它的左子树中所有的关键字都小于Node的关键字,而右子树中的所有关键字都大于Node的关键字. 二叉查找树的平均深度是O(log N). 1.初始化 class Bi ...

  2. go语言使用redis —— redigo

    redis的client有好多好多,go语言的client在redis官方有两个推荐,radix和redigo.选择哪一个好呢?确实很纠结,后来掷硬币决定选择redigo了. redis.go.red ...

  3. SelectList

    SelectList 构造函数 (IEnumerable, String, String, Object) 使用列表的指定项.数据值字段.数据文本字段和选定的值来初始化 SelectList 类的新实 ...

  4. How to open MS word document from the SharePoint 2010 using Microsoft.Office.Interop.dll

    or this you must change the identity of word component inC:\windows\System32\comexp.mscto be interac ...

  5. 如何正确看待Linq的DistinctBy扩展和ForEach扩展

    在微软标准的Linq中,并没有DistinctBy扩展和ForEach扩展,但在平时使用工作中却又经常需要使用到这两个功能,照理来说,微软在Linq中应该包含这两个扩展才对,可事实上为什么并没有呢?本 ...

  6. hibernate映射文件基础

    一.利用hibernate的eclipse插件快速生成实体类与配置文件的方法 1.首先下载安装和自己的eclipse版本配套的hibernate tools,如果是Myeclipse,在/readme ...

  7. 团体程序设计天梯赛-练习集L1-012. 计算指数

    L1-012. 计算指数 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 真的没骗你,这道才是简单题 —— 对任意给定的不超过1 ...

  8. EhCache 分布式缓存/缓存集群(转)

    开发环境: System:Windows JavaEE Server:tomcat5.0.2.8.tomcat6 JavaSDK: jdk6+ IDE:eclipse.MyEclipse 6.6 开发 ...

  9. DLL 支持MFC 没有DLLMAIN函数

    如果使用VC编写DLL时,需要MFC功能: 一般在源文件里就不能手动写DLLMAIN函数了 它给MFC集成了,\src\mfc\dllmodule.cpp打开它,里面有有一个DLLMAIN函数,根据源 ...

  10. Android SlidingMenu的getSupportActionBar无法找到的解决

    getSupportActionBar()方法不能用 进入Library中的src下找到SlidingFragmentActivity.java,修改 public class SlidingFrag ...