最常用的方式:

        int a = 12;
//注意:通常情况下,这个会设置成一个类变量,比如说Segement中的段锁与copyOnWriteArrayList中的全局锁
final ReentrantLock lock = new ReentrantLock(); lock.lock();//获取锁
try {
a++;//业务逻辑
} catch (Exception e) {
}finally{
lock.unlock();//释放锁
}

注:关于lock()方法的源码解析,请参照"第五章 ReentrantLock源码解析1--获得非公平锁与公平锁lock()",具体链接如下:

http://www.cnblogs.com/java-zhao/p/5131544.html

释放锁:unlock()

步骤:

1)获取当前的锁数量,然后用这个锁数量减去解锁的数量(这里为1),最后得出结果c

2)判断当前线程是不是独占锁的线程,如果不是,抛出异常

3)如果c==0,说明锁被成功释放,将当前的独占线程置为null,锁数量置为0,返回true

4)如果c!=0,说明释放锁失败,锁数量置为c,返回false

5)如果锁被释放成功的话,唤醒距离头节点最近的一个非取消的节点

源代码:

ReentrantLock:unlock()

    /**
* 释放这个锁
*1)如果当前线程持有这个锁,则锁数量被递减
*2)如果递减之后锁数量为0,则锁被释放。
*如果当前线程不持久有这个锁,抛出异常
*/
public void unlock() {
sync.release(1);
}

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

Sync:tryRelease(int releases)

        /**
* 释放锁
*/
protected final boolean tryRelease(int releases) {
int c = getState() - releases;//获取现在的锁数量-传入的解锁数量(这里为1)
if (Thread.currentThread() != getExclusiveOwnerThread())//当前线程不持有锁
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {//锁被释放
free = true;
setExclusiveOwnerThread(null);
}//如果不为0,怎么办,不释放了吗?
setState(c);
return free;
}

AbstractQueuedSynchronizer:unparkSuccessor(Node node)

    /**
* 唤醒离头节点node最近的一个非取消的节点
* @param node 头节点
*/
private void unparkSuccessor(Node node) { int ws = node.waitStatus;
if (ws < 0)//将ws设为0状态(即什么状态都不是)
compareAndSetWaitStatus(node, ws, 0); /*
* 获取头节点的下一个等待状态不是cancel的节点
*/
Node s = node.next;//头节点的下一个节点
if (s == null || s.waitStatus > 0) {
s = null;
/*
* 注意:从后往前遍历找到离头节点最近的一个非取消的节点,从后往前遍历据说是在入队(enq())的时候,可能nodeX.next==null,但是在读源码的时候没看出来
*/
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);//唤醒离头节点最近的一个非取消的节点
}

注意:

在程序的注释部分有一些疑问,整理成下边这样:

  • 如果按照开头的那个程序的话,成功的获取一个锁之后,就会在finally里边解一次锁,可重入性怎么体现?
  • 在找到离头节点最近的一个非取消的节点,是以从后往前的方式进行的,原因是"从后往前遍历据说是在入队(enq())的时候,可能nodeX.next==null",但是在读源码的时候没看出来

 第一个问题答案:

可重入性体现在下边这个程序(就是锁套锁,最常见的就是在递归中):

    final ReentrantLock lock = new ReentrantLock();
public void add(){
lock.lock();//获取锁
try {
add();//业务逻辑
} catch (Exception e) {
}finally{
lock.unlock();//释放锁
}
}

注意:

  • 上边这个程序只是一个示例,在递归的使用中,一定要有递归结束的条件
  • 每有一个lock()方法,就有一个unlock()与之对应,所以在解锁的时候,只需要把传递解锁数量为1就可以。

第二个问题答案:

记住:如果顺着节点头一直next下去可能会不正确。
举个例子:A1->A2->A3
现在我们从A1开始往下走,当我们走到A3的时候,就在这时,一个新节点A4入队,会走下面的入队代码:

可以看到步骤1和步骤2整体并不是原子的,也就是说,当执行完CAS的时候但是2还没执行,这时候队列为:A1-->A2-->A4,如果你使用next的话,可能就把A3给没了,但是node.prev = t(即A4.prev = A3),也就是说前驱节点是已经赋过值了的,如果你从队列结尾A4.prev就会是A3,即A3也丢不了。

第六章 ReentrantLock源码解析2--释放锁unlock()的更多相关文章

  1. 第六章 HashSet源码解析

    6.1.对于HashSet需要掌握以下几点 HashSet的创建:HashSet() 往HashSet中添加单个对象:即add(E)方法 删除HashSet中的对象:即remove(Object ke ...

  2. 第五章 ReentrantLock源码解析1--获得非公平锁与公平锁lock()

    最常用的方式: int a = 12; //注意:通常情况下,这个会设置成一个类变量,比如说Segement中的段锁与copyOnWriteArrayList中的全局锁 final Reentrant ...

  3. 第十四章 Executors源码解析

    前边两章介绍了基础线程池ThreadPoolExecutor的使用方式.工作机理.参数详细介绍以及核心源码解析. 具体的介绍请参照: 第十二章 ThreadPoolExecutor使用与工作机理 第十 ...

  4. 第九章 LinkedBlockingQueue源码解析

    1.对于LinkedBlockingQueue需要掌握以下几点 创建 入队(添加元素) 出队(删除元素) 2.创建 Node节点内部类与LinkedBlockingQueue的一些属性 static ...

  5. 死磕 java同步系列之ReentrantLock源码解析(二)——条件锁

    问题 (1)条件锁是什么? (2)条件锁适用于什么场景? (3)条件锁的await()是在其它线程signal()的时候唤醒的吗? 简介 条件锁,是指在获取锁之后发现当前业务场景自己无法处理,而需要等 ...

  6. Java并发之ReentrantLock源码解析(四)

    Condition 在上一章中,我们大概了解了Condition的使用,下面我们来看看Condition再juc的实现.juc下Condition本质上是一个接口,它只定义了这个接口的使用方式,具体的 ...

  7. ReentrantLock源码解析

    ReentrantLock 1 数据结构 从上图可以看出,ReentrantLock的功能都是通过sync这个对象提供的. public class ReentrantLock implements ...

  8. 深入浅出ReentrantLock源码解析

    ReentrantLock不但是可重入锁,而且还是公平或非公平锁,在工作中会经常使用到,将自己对这两种锁的理解记录下来,希望对大家有帮助. 前提条件 在理解ReentrantLock时需要具备一些基本 ...

  9. Java并发之ReentrantLock源码解析(二)

    在了解如何加锁时候,我们再来了解如何解锁.可重入互斥锁ReentrantLock的解锁方法unlock()并不区分是公平锁还是非公平锁,Sync类并没有实现release(int arg)方法,这里会 ...

随机推荐

  1. java POI创建Excel示例(xslx和xsl区别 )

    Java用来处理office类库有很多,其中POI就是比较出名的一个,它是apache的类库,现在版本到了3.10,也就是2014年2月8号这个版本. 在处理PPT,Excel和Word前,需要导入以 ...

  2. spring配置数据库连接池druid

    连接池原理 连接池基本的思想是在系统初始化的时候,将数据库连接作为对象存储在内存中,当用户需要访问数据库时,并非建立一个新的连接,而是从连接池中取出一个已建立的空闲连接对象.使用完毕后,用户也并非将连 ...

  3. tp中引入js、css、img的问题

    方法一: 直接把js.css.img放到网站公共目录/Public/下. 然后直接在模板文件中使用__PUBLIC__进行替换. 方法二: 在模块配置文件config.php中配置指定的路径,如下: ...

  4. 【转】HttpRuntime的认识与加深理解

    原文:http://www.cnblogs.com/whtydn/archive/2009/10/16/1584418.html   下面最先介绍HttpRuntime的Web.config里的配置 ...

  5. 【深度好文】多线程之WaitHandle-->派生-》Mutex信号量构造

    bool flag = false; System.Threading.Mutex mutex = new System.Threading.Mutex(true, "Test", ...

  6. Laravel图表扩展包推荐:Charts

     2016年11月15日 ·  2283次 ·  4条 ·  laravel,package,charts 介绍 在项目开发中,创建图表通常是一件痛苦的事情.因为你必须将数据转换为图表库支持的格式传输 ...

  7. 全国各地dns服务器列表

    211.103.13.101 江苏省无锡市 移动DNS服务器 211.136.28.231 北京市 移动DNS服务器 211.136.28.234 北京市 移动DNS服务器 211.136.28.23 ...

  8. MATLAB实现截位的问题

    讨论MATLAB怎样提取10进制中的位的方法,因为做FFT时要用到截位,相去验证它,向同庆请教, 原来只是除以2的N次方,取模取余就行了,可恨我还想了一下午,也没有一个好办法. 接下来的问题是,对于负 ...

  9. 信息管理代码分析<二>读取二进制文件数据

    first和end做为全局变量,分别指向链表的头和尾.建立链表的方式也比较简易,从二进制文件数据块中,依次从头到尾读取,每读取一个就建立一个结点. /*基本模型*/ EMP *emp1; while( ...

  10. ssh远程调用之shell脚本远程调用应用程序

    1.引子 前几天有一个需求是这样的:本机的shell脚本,通过远程调用另一台机子上的shell脚本,来完成对远程机子上分发的Java程序的执行和其他操作.看上去挺容易,实际上也不难. 第一步:用scp ...