Java多线程系列--“JUC锁”04之 公平锁(二)

释放公平锁(基于JDK1.7.0_40)

1. unlock()

unlock()在ReentrantLock.java中实现的,源码如下:

public void unlock() {
sync.release(1);
}

说明
unlock()是解锁函数,它是通过AQS的release()函数来实现的。
在这里,“1”的含义和“获取锁的函数acquire(1)的含义”一样,它是设置“释放锁的状态”的参数。由于“公平锁”是可重入的,所以对于同一个线程,每释放锁一次,锁的状态-1。

关于AQS, ReentrantLock 和 sync的关系如下:

public class ReentrantLock implements Lock, java.io.Serializable {

    private final Sync sync;

    abstract static class Sync extends AbstractQueuedSynchronizer {
...
} ...
}

从中,我们发现:sync是ReentrantLock.java中的成员对象,而Sync是AQS的子类。

2. release()

release()在AQS中实现的,源码如下:

public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}

说明
release()会先调用tryRelease()来尝试释放当前线程锁持有的锁。成功的话,则唤醒后继等待线程,并返回true。否则,直接返回false。

3. tryRelease()

tryRelease()在ReentrantLock.java的Sync类中实现,源码如下:

protected final boolean tryRelease(int releases) {
// c是本次释放锁之后的状态
int c = getState() - releases;
// 如果“当前线程”不是“锁的持有者”,则抛出异常!
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException(); boolean free = false;
// 如果“锁”已经被当前线程彻底释放,则设置“锁”的持有者为null,即锁是可获取状态。
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// 设置当前线程的锁的状态。
setState(c);
return free;
}

说明
tryRelease()的作用是尝试释放锁。
(01) 如果“当前线程”不是“锁的持有者”,则抛出异常。
(02) 如果“当前线程”在本次释放锁操作之后,对锁的拥有状态是0(即,当前线程彻底释放该“锁”),则设置“锁”的持有者为null,即锁是可获取状态。同时,更新当前线程的锁的状态为0。
getState(), setState()在前一章已经介绍过,这里不再说明。
getExclusiveOwnerThread(), setExclusiveOwnerThread()在AQS的父类AbstractOwnableSynchronizer.java中定义,源码如下:

public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable { // “锁”的持有线程
private transient Thread exclusiveOwnerThread; // 设置“锁的持有线程”为t
protected final void setExclusiveOwnerThread(Thread t) {
exclusiveOwnerThread = t;
} // 获取“锁的持有线程”
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
} ...
}

4. unparkSuccessor()

在release()中“当前线程”释放锁成功的话,会唤醒当前线程的后继线程。
根据CLH队列的FIFO规则,“当前线程”(即已经获取锁的线程)肯定是head;如果CLH队列非空的话,则唤醒锁的下一个等待线程。
下面看看unparkSuccessor()的源码,它在AQS中实现。

private void unparkSuccessor(Node node) {
// 获取当前线程的状态
int ws = node.waitStatus;
// 如果状态<0,则设置状态=0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0); //获取当前节点的“有效的后继节点”,无效的话,则通过for循环进行获取。
// 这里的有效,是指“后继节点对应的线程状态<=0”
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = 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);
}

说明
unparkSuccessor()的作用是“唤醒当前线程的后继线程”。后继线程被唤醒之后,就可以获取该锁并恢复运行了。
关于node.waitStatus的说明,请参考“上一章关于Node类的介绍”。

注:unlock最好放在finally中!!!!!!unlock最好放在finally中!!!!!! unlock最好放在finally中!!!!!! (重要的事说三遍)

释放非公平锁(基于JDK1.7.0_40)

非公平锁和公平锁在释放锁的方法和策略上是一样的。
而在前面的“Java多线程系列--“JUC锁”04之 公平锁(二) ”中,已经对“释放公平锁”进行了介绍;这里就不再重复的进行说明。

总结

“释放锁”的过程相对“获取锁”的过程比较简单。释放锁时,主要进行的操作,是更新当前线程对应的锁的状态。如果当前线程对锁已经彻底释放,则设置“锁”的持有线程为null,设置当前线程的状态为空,然后唤醒后继线程。

Java - "JUC" ReentrantLock释放锁的更多相关文章

  1. Java - "JUC" ReentrantLock获取锁

    [Java并发编程实战]-----“J.U.C”:ReentrantLock之一简介 ReentrantLock介绍 ReentrantLock是一个可重入的互斥锁,又被称为“独占锁”. 顾名思义,R ...

  2. Java关于ReentrantLock获取锁和释放锁源码跟踪

    通过对ReentrantLock获取锁和释放锁源码跟踪主要想进一步深入学习AQS. 备注:AQS中的waitStatus状态码含义:

  3. Java之ReentrantLock公平锁和非公平锁

    在Java的ReentrantLock构造函数中提供了两种锁:创建公平锁和非公平锁(默认).代码如下: public ReentrantLock() { sync = new NonfairSync( ...

  4. java高并发系列 - 第12天JUC:ReentrantLock重入锁

    java高并发系列 - 第12天JUC:ReentrantLock重入锁 本篇文章开始将juc中常用的一些类,估计会有十来篇. synchronized的局限性 synchronized是java内置 ...

  5. Java并发编程(3) JUC中的锁

    一 前言 前面已经说到JUC中的锁主要是基于AQS实现,而AQS(AQS的内部结构 .AQS的设计与实现)在前面已经简单介绍过了.今天记录下JUC包下的锁是怎么基于AQS上实现的 二 同步锁 同步锁不 ...

  6. 【Java并发编程实战】----- AQS(二):获取锁、释放锁

    上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放. AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断. acquireInte ...

  7. 轻松学习java可重入锁(ReentrantLock)的实现原理

    转载自https://blog.csdn.net/yanyan19880509/article/details/52345422,(做了一些补充) 前言 相信学过java的人都知道 synchroni ...

  8. java 可重入锁ReentrantLock的介绍

    一个小例子帮助理解(我们常用的synchronized也是可重入锁) 话说从前有一个村子,在这个村子中有一口水井,家家户户都需要到这口井里打水喝.由于井水有限,大家只能依次打水.为了实现家家有水喝,户 ...

  9. 轻松学习java可重入锁(ReentrantLock)的实现原理(转 图解)

    前言 相信学过java的人都知道 synchronized 这个关键词,也知道它用于控制多线程对并发资源的安全访问,兴许,你还用过Lock相关的功能,但你可能从来没有想过java中的锁底层的机制是怎么 ...

随机推荐

  1. [SDOI2011]消耗战(虚树+树形动规)

    虚树dp 虚树的主要思想: 不遍历没用的的节点以及没用的子树,从而使复杂度降低到\(\sum\limits k\)(k为询问的节点的总数). 所以怎么办: 只把询问节点和其LCA放入询问的数组中. 1 ...

  2. css居中小结

    从css入门就开始接触,无所不在的,一直备受争议的居中问题. css居中分为水平居中和垂直居中,水平居中方式也较为常见和统一,垂直居中的方法就千奇百怪了. 博客原文地址:Claiyre的个人博客 ht ...

  3. postgresql-pg_prewarm数据预加载。

    pg_prewarm数据预加载. http://francs3.blog.163.com/blog/static/405767272014419114519709/   https://www.kan ...

  4. 深入理解java虚拟机读书笔记--java内存区域和管理

    第二章:Java内存区域和内存溢出异常 2.2运行时数据区域 运行时数据区分为方法区,堆,虚拟机栈,本地方法栈,程序计数器 方法区和堆是线程共享的区域 虚拟机栈,本地方法栈,程序计数器是数据隔离的数据 ...

  5. 初识java java的加载与执行(JDK,JVM,JRE关系解释)

    首先java代码是以 .java结尾的文件,通过javac命令编译生成.class编译生成字节码文件,再通过java命令,把字节码文件加载到内存内部,此时是类加载器ClassLoader执行加载,通过 ...

  6. 【liferay】6、关于liferay中使用requestMapping映射地址提交表单

    1.接着上一篇博客,从新在定义一下页面 <%@ page contentType="text/html; charset=utf-8" language="java ...

  7. JobScheduler调度器过程(JobSchedulerService的启动过程)

    JobSchedulerService启动过程,最主要工作是从jobs.xml文件收集所有的jobs,放入到JobStore的成员变量mJobSet,转成jobinfo. JobScheduler服务 ...

  8. 用SpringSecurity从零搭建pc项目-02

    参照这一篇文章吧,比如你不需要做的那么通用,取其中一部分代码即可. https://www.cnblogs.com/lihaoyang/p/8491792.html

  9. Kafka消费异常处理

    org.apache.kafka.clients.consumer.CommitFailedException: Commit cannot be completed since the group ...

  10. Python学习--12 异常处理、调试

    异常捕获 语法格式: try: pass except xxx as e: pass except xxx as e: pass ... else: pass finally: pass except ...