ReentrantLock(重入锁)以及公平性

标签(空格分隔): java NIO


如果在绝对时间上,先对锁进行获取的请求一定被先满足,那么这个锁是公平的,反之,是不公平的,也就是说等待时间最长的线程最有机会获取锁,也可以说锁的获取是有序的。ReentrantLock这个锁提供了一个构造函数,能够控制这个锁是否是公平的。

而锁的名字也是说明了这个锁具备了重复进入的可能,也就是说能够让当前线程多次的进行对锁的获取操作,这样的最大次数限制是Integer.MAX_VALUE,约21亿次左右。

具体实现

非公平:

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;
}
//状态不为0(锁被占用中),当前线程不是持有线程,返回失败
//即 锁正被别的线程占用,则请求失败
return false;
}

公平:

protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//区别在于这里多了一项判断:是否是等待队列里的第一个
//效果是:如果锁被自己的线程持有着,仍然可以重入(即'else-if')。
//但如果锁时空闲着的,则先来先得(排在队首)
if (isFirst(current) && 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;
}

进行lock操作时

/*ReentrantLock->NonfairSync */
final void lock() {
//如果锁空闲,直接使用,无需排队;否则进行请求
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
} /*AbstractQueuedSynchronizer */
public final void acquire(int arg) {
//注意,A&B运算,如果A==False,则不会执行B
//因此,如果tryAcquire成功,则不执行后面的“在sync队列中排队”操作
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}

上述代码,即非公平的acquire,如下图所示

  • Fast:tryAcquire,非公平的获取
  • normal: 进入请求队列

    这意味着,某条线程释放完锁,也可以不经过等待队列,而通过Fast通道再次获得锁。

这种模式,可以保证进入和退出锁的吞吐量,但是sync队列中过早排队的线程会一直处于阻塞状态,造成“饥饿”场景。在非公平锁中,CPU的处理时间中,只有很少的时间花在线程调度上,大多都用在实际工作上。


非公平锁性能高于公平锁性能的原因

在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟。

考虑如下场景:线程A释放了锁,队列中第一个线程B将从[挂起]状态向[唤醒]状态,并再次请求锁。在B未完成唤醒前,线程C也开始请求锁。

  • 公平锁:虽然锁现在是空闲的,但因为队列中还有线程B排在前面,因此C需进队列等待,状态变为[挂起]。从‘C到来’至‘B完全唤醒’之间的时间T,锁是空闲的,被浪费了。
  • 非公平锁:由于此时锁是空闲的,C直接获得锁。如果C工作执行的快,可能当释放锁之后,B尚未完全唤醒,则原本会被浪费的时间T被利用了。但如果B唤醒后C还未执行完,则B又要挂起继续等待。

因此,当持有锁的时间相对较长或者请求锁的平均时间间隔较长,应该使用公平锁。在这些情况下,插队带来的吞吐量提升(当锁处于可用状态时,线程却还处于被唤醒的过程中)可能不会出现。

ReentrantLock(重入锁)以及公平性的更多相关文章

  1. 从源码角度彻底理解ReentrantLock(重入锁)

    目录 1.前言 2.AbstractQueuedSynchronizer介绍 2.1 AQS是构建同步组件的基础 2.2 AQS的内部结构(ReentrantLock的语境下) 3 非公平模式加锁流程 ...

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

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

  3. java并发系列(四)-----源码角度彻底理解ReentrantLock(重入锁)

    1.前言 ReentrantLock可以有公平锁和非公平锁的不同实现,只要在构造它的时候传入不同的布尔值,继续跟进下源码我们就能发现,关键在于实例化内部变量sync的方式不同,如下所示: /** * ...

  4. ReentrantLock(重入锁)的源码解析

    转自:从源码角度彻底理解ReentrantLock(重入锁)](https://www.cnblogs.com/takumicx/p/9402021.html)) 公平锁内部是FairSync,非公平 ...

  5. ReentrantLock重入锁详解

    1.定义 重入锁:能够支持一个线程对资源的重复加锁,也就是当一个线程获取到锁后,再次获取该锁时而不会被阻塞. 2.可重入锁的应用场景 2.1 如果已经加锁,则不再重复加锁,比如:交互界面点击后响应时间 ...

  6. Java多线程之ReentrantLock重入锁简介与使用教程

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6543947.html  我们知道,线程安全问题需要通过线程之间的同步来解决,而同步大多使用syncrhoize ...

  7. ReentrantLock 重入锁(下)

    前沿:       ReentrantLock 是java重入锁一种实现,在java中我们通常使用ReentrantLock 和 synchronized来实现锁功能,本篇通过例子来理解下Reentr ...

  8. ReentrantLock(重入锁)简单源码分析

    1.ReentrantLock是基于AQS实现的一种重入锁. 2.先介绍下公平锁/非公平锁 公平锁 公平锁是指多个线程按照申请锁的顺序来获取锁. 非公平锁 非公平锁是指多个线程获取锁的顺序并不是按照申 ...

  9. [图解Java]ReentrantLock重入锁

    图解ReentrantLock 0. demo 我先给出一个demo, 这样大家就可以根据我给的这段代码, 边调试边看源码了. 还是那句话: 注意"My" , 我把Reentran ...

随机推荐

  1. python学习笔记:python对象

    一.python对象 python使用对象模型来存储数据,构造任何类型的值都是一个对象.所有的python对象都拥有三个特性:身份.类型和值. 身份:每个对象都有一个唯一的身份标识自己,对象的身份可以 ...

  2. skynet的流程2

    http://blog.csdn.net/xiarendeniao/article/details/38613161 http://www.jiandan.ren/2015/06/skynet-hel ...

  3. Java之线程的控制

    1. join线程: 在线程执行过程中,有时想让另一个线程先执行,比如将一大问题分割成许多小问题,给每一个小问题分配线程,但所有小问题处理完后再让主线程进一步操作.此时我们可以在主线程中调用其它线程的 ...

  4. mac下配置java环境

    1.tomcat配置 http://www.cnblogs.com/freeyiyi1993/p/3436368.html 2.下载eclipse和jdk安装  jdk去oracle网站下载

  5. Delphi实现全局鼠标钩子

    其中涉及到的一些API,网上均能查到详细的解释,这里不再熬述.源码下载 因为是全局钩子,所以要用dll注入.用到的鼠标消息结构如下: PMouseHookStruct = ^TMouseHookStr ...

  6. getopt vs getopts

    getopt示例 #!/bin/bash aflag=no args=`getopt a: $@` ]; then echo 'Usage: ...' exit fi set -- $args ] d ...

  7. linux学习之(四)-用户、组的操作,给文件文件夹设置组,更改目录权限、文件权限

    命令帮助查看: man 命令(查看一个命令的详细帮助信息) 例:man useradd 或者用  -h   格式   命令 -h(查看一个命令的简要帮助) 例:useradd -h 用户: 在user ...

  8. android 的线程模型和AsyncTask

            android 的线程模型:当一个 android 的应用运行后,就会有一个 UI 的 main 线程启动 , 这是一个非常重要的线程,它负责把事件分派到相应的控件,其中就包括屏幕绘图 ...

  9. 裸的单调队列-poj-2823-Sliding Window

    题目链接: http://poj.org/problem?id=2823 题目意思: 给n个数,求连续区间长度为k的最大和最小值. 解题思路: 裸的单调队列不解释,用两个队列保存. 代码: #incl ...

  10. exit函数的妙用

    写了一个程序,用来推断一个文件是否存在: #include<stdio.h> main() {  FILE *fp;  fp = fopen ("/home/wang/my/ct ...