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 模拟ajax查询社工库...

    在windows中使用,输入有关信息查询社工库,本来是网页版的,我把ajax请求提取出来.粗略的封装下,挺好玩. #coding:utf8 import urllib2,urllib from Bea ...

  2. C++数据类型总结

    关键字:C++, 数据类型, VS2015. OS:Windows 10. ANSI C/C++基本数据类型: Type Size 数值范围 无值型void 0 byte 无值域 布尔型bool 1 ...

  3. django1.6之template基础用法

    >>> settings.configure()>>> tem=template.Template("my template is {{name}}&qu ...

  4. OFBIZ bug_create-component ERROR

    开发环境:win7 64位 Eclipse 运行create-component,报一下错误: Buildfile: F:\workspace\opensource\apache-obiz\apach ...

  5. 【非原】c语言之声明和定义的区别

    原创地址:http://www.cnblogs.com/haore147/p/3647466.html 什么是定义?什么是声明?它们有何区别? 举个例子: 1 2 A)int i; B)extern  ...

  6. Lua基础之table详解

    概要:1.table特性:2.table的构造:3.table常用函数:4.table遍历:5.table面向对象 原文地址:http://blog.csdn.net/dingkun520wy/art ...

  7. 解决mybatis查询返回结果值串查

    方式一: 通过as 指定 大写重名列的 别名 方式二: 命名数据库中表名时 每个表的主键 id 要起不同的名称, 避免主键重复(但是子表的外键可以和主表的id主键重名, 你想啊, 从表的外键性质不就是 ...

  8. sqlserver自定义函数【粘】

     用户定义自定义函数像内置函数一样返回标量值,也可以将结果集用表格变量返回 用户自定义函数的类型: 标量函数:返回一个标量值 表格值函数{内联表格值函数.多表格值函数}:返回行集(即返回多个值) 1. ...

  9. 如何通过logcat查看系统程序的意图

    如果在logcat中不能看到系统程序启动时的意图的类名, 以打开图库(gallery)为例,可以通过在ddms中如图设置,就可以在tomcat中查看到gallery启动时的意图.

  10. JAVA-前台编码,后台解码

    前台 var objValue =window.encodeURI(window.encodeURI(queryObj)); alert(objValue); 后台 String descStr = ...