JUC回顾之-可重入的互斥锁ReentrantLock
1.什么是可重锁ReentrantLock?
就是支持重新进入的锁,表示该锁能够支持一个线程对资源的重复加锁。底层实现原理主要是利用通过继承AQS来实现的,也是利用通过对volatile state的CAS操作+CLH队列来实现;
支持公平锁和非公平锁。
CAS:Compare and Swap 比较并交换。CAS的思想很简单:3个参数,一个当前内存值V、预期值A,即将更新的值B,当前仅当预期值A和内存值V相等的时候,将内存值V修改为B,否则什么都不做。该操作是一个原子操作被广泛的用于java的底层实现中,在java中,CAS主要是有sun.misc.Unsafe这个类通过JNI调用CPU底层指令实现;更多底层的思想参考狼哥的文章cas的底层原理:https://www.jianshu.com/p/fb6e91b013cc
CLH队列:也叫同步队列,是带头结点的双向非循环列表,是AQS的主要实现原理(结构如下图所示)
2.ReentrantLock分为公平锁和非公平锁:区别是在于获取锁的机制上是否公平。
(1)公平锁:公平的获取锁,也就是等待时间最长的线程最优获取到锁,ReentraantLock是基于同步队列AQS来管理获取锁的线程。
在公平的机制下,线程依次排队获取锁,先进入队列排队的线程,等到时间越长的线程最优获取到锁。
(2)非公平锁:而在“非公平”的机制下,在锁是可获取状态时,不管自己是否在对头都会获取锁。
(3) 公平锁和非公平锁的对比:1、公平锁用来解决“线程饥饿”的问题,即先进入CLH同步队列等待的线程,即同步队列中的头结点总是先获取到锁。而非公平锁 会出现 一个线程连续多次获取锁的情况,使得其他线程只能在同步队列中等待。
2、经过测试,10个线程,每一个线程获取100 000次锁,通过vmstat统计运行时系统线程上下文切换的次数;公平锁总耗时 为:5754ms,而费公平锁总耗时为61ms。总耗时:公平锁/费公平锁=94.3倍,总切换次数 :公平锁/费公平锁=133倍。非 公平锁的线程切换更少,保证了更大的吞吐量。
(4)可重入锁的结构如下:
3.非公平锁获取的实现源码如下:按照代码的执行顺序
(1)调用lock方法
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L; /**
* 1.Performs lock. Try immediate barge, backing up to normal
* acquire on failure.调用lock方法
*/
final void lock() {
//判断当前state是否为0,即没有被任何线程获取的状态,如果是,CAS更新为1,当前线程获取到了非公平锁
if (compareAndSetState(0, 1))
//设置当前锁的持有者线程
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
}
(2)如果状态不是0,CAS更新状态放回false走acquire(1)方法:
在讲aqs的时候说过这个方法,这里不做重复:
/**2 获取非公平锁
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
(3).调用静态内部类NonfairSync重写AQS的tryAcquire(1)方法:
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L; //3.调用静态内部类NonfairSync重写AQS的tryAcquire方法
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
(4) 走nonfairTryAcquire(1)非公平锁的实现方法:重点分析下这个方法
/**
* Performs non-fair tryLock. tryAcquire is
* implemented in subclasses, but both need nonfair
* try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取锁的状态
int c = getState();
//c == 0 说明锁没有被任何线程所拥有,则CAS设置锁的状态为acquires
if (c == 0) {
if (compareAndSetState(0, acquires)) {
//设置当前线程为锁的拥有者
setExclusiveOwnerThread(current);
return true;
}
}
//如果锁的持有者已经是当期线程,更新锁的状态,这个地方就是为什么可重入的原因,如果获取锁的线程再次请求,则将同步状态的值增加,并返回true
//表明获取同步状态值成功
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
4.获取公平锁的过程
(1)公平锁源码获取源码如下:
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
//直接调用acquire(1)方法
final void lock() {
acquire(1);
} /**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//获取独占锁的状态
int c = getState();
if (c == 0) {
//c==0表示锁没有被任何线程锁拥有,首先判断当前线程是否为CLH同步队列的第一个线程;是的话,获取该锁,设置锁的状态
if (!hasQueuedPredecessors() &&
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;
}
}
从源码可以看出,和nonfairTryAcquire(int acquires)比较唯一不同的是 ,判断条件多了hasQueuePredecessors()方法,加入了当前节点是否有前驱节点的判断。如果返回true,表示有线程比当前线程等待的时间长,需要等待前驱线程获取并释放锁之后才能获取锁。返回false,表示当前的线程所在的节点为队列(CLH队列)的对头,可以直接获取锁。
/**
* Queries whether any threads have been waiting to acquire longer
* than the current thread.
*
* <p>An invocation of this method is equivalent to (but may be
* more efficient than):
* <pre> {@code
* getFirstQueuedThread() != Thread.currentThread() &&
* hasQueuedThreads()}</pre>
*
* <p>Note that because cancellations due to interrupts and
* timeouts may occur at any time, a {@code true} return does not
* guarantee that some other thread will acquire before the current
* thread. Likewise, it is possible for another thread to win a
* race to enqueue after this method has returned {@code false},
* due to the queue being empty.
*
* <p>This method is designed to be used by a fair synchronizer to
* avoid <a href="AbstractQueuedSynchronizer#barging">barging</a>.
* Such a synchronizer's {@link #tryAcquire} method should return
* {@code false}, and its {@link #tryAcquireShared} method should
* return a negative value, if this method returns {@code true}
* (unless this is a reentrant acquire). For example, the {@code
* tryAcquire} method for a fair, reentrant, exclusive mode
* synchronizer might look like this:
*
* <pre> {@code
* protected boolean tryAcquire(int arg) {
* if (isHeldExclusively()) {
* // A reentrant acquire; increment hold count
* return true;
* } else if (hasQueuedPredecessors()) {
* return false;
* } else {
* // try to acquire normally
* }
* }}</pre>
*
* @return {@code true} if there is a queued thread preceding the
* current thread, and {@code false} if the current thread
* is at the head of the queue or the queue is empty
* @since 1.7
*/
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
4.释放锁:
(1)首先调用ReetrantLock重写父类AQS的unlock方法:
/**
* Attempts to release this lock.
*
* <p>If the current thread is the holder of this lock then the hold
* count is decremented. If the hold count is now zero then the lock
* is released. If the current thread is not the holder of this
* lock then {@link } is thrown.
*
* @throws IllegalMonitorStateException if the current thread does not
* hold this lock
*/
public void unlock() {
sync.release(1);
}
(2)调用AQS里面实现的释放互斥锁的方法:首先进入tryRelease()方法来尝试释放当前线程持有的锁,如果成功的话,调用unparkSuccessor唤醒后继线程。
/**
* Releases in exclusive mode. Implemented by unblocking one or
* more threads if {@link #tryRelease} returns true.
* This method can be used to implement method {@link Lock#unlock}.
*
* @param arg the release argument. This value is conveyed to
* {@link #tryRelease} but is otherwise uninterpreted and
* can represent anything you like.
* @return the value returned from {@link #tryRelease}
*/
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
(3)进入tryRelease方法我们重点分析:重写父类AQS里面的模板方法,进行锁的释放:
protected final boolean tryRelease(int releases) {
//c是本次释放锁之后的状态
int c = getState() - releases;
// 如果当前线程不是锁的持有者线程,则抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//c==0表示锁被当前线程已经彻底释放,则将占有同步状态的线程设置为Null,即锁变为可获取的状态,这个时候才能返回true,否则返回false
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//否则设置同步状态
setState(c);
return free;
}
(4)释放锁成功后,即锁变为可获取的状态后,调用unparkSuccessor唤醒后继线程,进入unparkSuccessor的源码:
/**
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0); /*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
获取当前节点的有效的后继节点,这的有效是指后继节点s不为null并且waitStatus是<=0的,
既没有被取消的状态。无效的话,通过for循环遍历,一直找到一个有效的节点
*/
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);
}
常用的方法:
1.创建一个 ReentrantLock ,默认是“非公平锁”。
ReentrantLock()
2. 创建策略是fair的 ReentrantLock。fair为true表示是公平锁,fair为false表示是非公平锁。
ReentrantLock(boolean fair)
3. 查询当前线程保持此锁的次数。
int getHoldCount()
4. 返回目前拥有此锁的线程,如果此锁不被任何线程拥有,则返回 null。
protected Thread getOwner()
5.返回目前拥有此锁的线程,如果此锁不被任何线程拥有,则返回 null。
protected Thread getOwner()
6.返回一个 collection,它包含可能正等待获取此锁的线程。
protected Collection<Thread> getQueuedThreads()
7.返回正等待获取此锁的线程估计数。
int getQueueLength()
8.返回一个 collection,它包含可能正在等待与此锁相关给定条件的那些线程。
protected Collection<Thread> getWaitingThreads(Condition condition)
9.返回等待与此锁相关的给定条件的线程估计数。
int getWaitQueueLength(Condition condition)
10.查询给定线程是否正在等待获取此锁。
boolean hasQueuedThread(Thread thread)
11.查询是否有些线程正在等待获取此锁。
boolean hasQueuedThreads()
12.查询是否有些线程正在等待与此锁有关的给定条件。
boolean hasWaiters(Condition condition)
13.如果是“公平锁”返回true,否则返回false。
boolean isFair()
14.查询当前线程是否保持此锁。
boolean isHeldByCurrentThread()
15.查询此锁是否由任意线程保持。
boolean isLocked()
16.获取锁。
void lock()
17.如果当前线程未被中断,则获取锁。
void lockInterruptibly()
18.返回用来与此 Lock 实例一起使用的 Condition 实例。
Condition newCondition()
19.仅在调用时锁未被另一个线程保持的情况下,才获取该锁。
boolean tryLock()
20.如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁。
boolean tryLock(long timeout, TimeUnit unit)
21.试图释放此锁。
void unlock()
应用多个线程的计数器:结果为 20000。
package concurrentMy.Volatiles; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; /**
*
* (类型功能说明描述)
*
* <p>
* 修改历史: <br>
* 修改日期 修改人员 版本 修改内容<br>
* -------------------------------------------------<br>
* 2016年4月8日 下午6:07:36 user 1.0 初始化创建<br>
* </p>
*
* @author Peng.Li
* @version 1.0
* @since JDK1.7
*/
public class VolatiteLock implements Runnable{
// 不能保证原子性,如果不加lock的话
private volatile int inc = 0;
Lock lock = new ReentrantLock(); /**
*
* 理解:高速缓存 - 主存
* 通过ReentrantLock保证原子性:读主存,在高速缓存中计算得到+1后的值,写回主存
* (方法说明描述)
*
*/
public void increase() {
lock.lock();
try {
inc++;
} catch (Exception e) {
e.printStackTrace();
}finally{
lock.unlock();
} } public void run() {
for (int i = 0; i < 10000; i++) {
increase();
} } public static void main(String[] args) throws InterruptedException { VolatiteLock v = new VolatiteLock();
// 线程1
Thread t1 = new Thread(v);
// 线程2
Thread t2 = new Thread(v);
t1.start();
t2.start(); // for(int i=0;i<100;i++){
// System.out.println(i);
// } System.out.println(Thread.activeCount() + Thread.currentThread().getId() + Thread.currentThread().getName()); while (Thread.activeCount() > 1)
// 保证前面的线程都执行完
Thread.yield(); //
System.out.println(v.inc);
} }
JUC回顾之-可重入的互斥锁ReentrantLock的更多相关文章
- Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock
本章对ReentrantLock包进行基本介绍,这一章主要对ReentrantLock进行概括性的介绍,内容包括:ReentrantLock介绍ReentrantLock函数列表ReentrantLo ...
- concurrent(三)互斥锁ReentrantLock & 源码分析
参考文档:Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock:http://www.cnblogs.com/skywang12345/p/3496101.html Reentr ...
- 多线程编程-- part5.1 互斥锁ReentrantLock
ReentrantLock简介 Reentrantlock是一个可重入的互斥锁,又被称为独占锁. Reentrantlock:分为公平锁和非公平锁,它们的区别体现在获取锁的机制上是否公平.“锁”是为了 ...
- java并发编程(一)可重入内置锁
每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁或监视器锁.线程在进入同步代码块之前会自动获取锁,并且在退出同步代码块时会自动释放锁.获得内置锁的唯一途径就是进入由这个锁保护的同步代码块 ...
- 转:【Java并发编程】之一:可重入内置锁
每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁或监视器锁.线程在进入同步代码块之前会自动获取锁,并且在退出同步代码块时会自动释放锁.获得内置锁的唯一途径就是进入由这个锁保护的同步代码块 ...
- 【Java并发编程】之一:可重入内置锁
每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁或监视器锁.线程在进入同步代码块之前会自动获取锁,并且在退出同步代码块时会自动释放锁.获得内置锁的唯一途径就是进入由这个锁保护的同步代码块 ...
- 【分布式锁】Redis实现可重入的分布式锁
一.前言 之前写的一篇文章<细说分布式锁>介绍了分布式锁的三种实现方式,但是Redis实现分布式锁关于Lua脚本实现.自定义分布式锁注解以及需要注意的问题都没描述.本文就是详细说明如何利用 ...
- 【漫画】互斥锁ReentrantLock不好用?试试读写锁ReadWriteLock
ReentrantLock完美实现了互斥,完美解决了并发问题.但是却意外发现它对于读多写少的场景效率实在不行.此时ReentrantReadWriteLock来救场了!一种适用于读多写少场景的锁,可以 ...
- JUC回顾之-ArrayBlockingQueue底层实现和原理
ArrayBlockingQueue的原理和底层实现的数据结构 : ArrayBlockingQueue是数组实现的线程安全的有界的阻塞队列,可以按照 FIFO(先进先出)原则对元素进行排序. 线程安 ...
随机推荐
- Jquery插件easyUi表单验证提交
<form id="myForm" method="post"> <table align="center" style= ...
- ExtJS入门教程04,这是一个超级好用的grid
今天进行extjs入门教程的第四篇:grid. 来一份grid尝尝 小伙伴们都知道extjs的grid功能强大,更清楚功能强大的东西用起来必然会复杂.今天我们就从最简单的grid开始讲解. 先来一个最 ...
- MySQL中concat函数
MySQL中concat函数使用方法:CONCAT(str1,str2,…) 返回结果为连接参数产生的字符串.如有任何一个参数为NULL ,则返回值为 NULL. 注意:如果所有参数均为非二进制字符串 ...
- 慎用 Enum.GetHashCode()
公司里遗留下了相当多的 Enum.GetHashCode()来获取枚举值的代码 但是这会产生装箱行为的!!因为Enum是值类型,GetHashCode()是Object的方法,调用GetHashCod ...
- WEB开发中的页面跳转方法总结
PHP header()函数跳转 PHP的header()函数非常强大,其中在页面url跳转方面也调用简单,使用header()直接跳转到指定url页面,这时页面跳转是302重定向: $url = & ...
- SQL防注入程序 v1.0
/// ***************C#版SQL防注入程序 v1.0************ /// *使用方法: /// 一.整站防注入(推荐) /// 在Global.asax.cs中查找App ...
- Dedecms v5.7 最新注入分析
该漏洞是cyg07在乌云提交的, 漏洞文件: plus\feedback.php.存在问题的代码: view source 01 ... 02 if($comtype == 'comments') 0 ...
- linux文件系统模拟
#include "stdio.h" #include <stdlib.h> //#include <conio.h> #include <strin ...
- C++中的异常处理(一)
来自:CSDN 卡尔 后续有C++中的异常处理(二)和C++中的异常处理(三),C++中的异常处理(二)是对动态分配内存后内部发生错误情况的处理方法,C++中的异常处理(三)中是使用时的异常说明. ...
- 如何快速读懂大型C++程序代码
要搞清楚别人的代码,首先,你要了解代码涉及的领域知识,这是最重要的,不懂领域知识,只看代码本身,不可能搞的明白.其次,你得找各种文档:需求文档(要做什么),设计文档(怎么做的),先搞清楚你即将要阅读是 ...