ReentrantLock是一个较为常用的锁对象。在上次分析的uil开源项目中也多次被用到,下面谈谈其概念和基本使用。

概念

一个可重入的互斥锁定 Lock,它具有与使用 synchronized 相同的一些基本行为和语义,但功能更强大。

名词解释:

互斥

表示同一时刻,多个线程中,只能有一个线程能获得该锁。但是多个线程都可以调用lock方法,只有一个会成功,其他的线程会被阻塞,直到该锁被释放

可重入

模仿synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续)synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。
对于ReentrantLock,每次获得锁,并将请求计数置为一,如果同一个线程再次lock,计数器将递增,每次unlock时计数器值递减,直到计数器为0,锁释放

lock方法过程

如果该锁没有被另一个线程保持,则lock时获取该锁定并立即返回,将锁定的保持计数设置为 1。
如果当前线程已经保持该锁定,则将保持计数加 1,并且该方法立即返回。
如果该锁定被另一个线程保持,则出于线程调度的目的,禁用当前线程,并且在获得锁定之前,该线程将一直处于休眠状态,此时锁定保持计数被设置为 1。

unLock方法过程

每次unlock时计数器值递减,直到计数器为0,释放锁

Condition类

该类与lock绑定,用newCondition()方法创建,提供了线程之间通信的方式(类似信号量)。其使用基本与object类的wait,notify,notifyAll相同。
用condition.await()替换Object,wait(),调用时该线程阻塞,释放该线程的锁。
用condition.signal()替换Object.notify(),用condition.signalAll()替换Object.notifyAll(),唤醒该condition await方法所阻塞的线程

相对synchronized优势

锁投票(我也不是特别理解,可以通过投票获取锁?)
定时锁等候
中断锁等候
线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,
如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断
如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情

使用

以下以linkedBlokingQueue源码为例子,来学习其使用。

public class LinkedBlockingQueue<E> extends AbstractQueue<E> implements BlockingQueue<E>, java.io.Serializable {
//链表节点node类结构
static class Node<E> {
volatile E item;//volatile,保证了数据的可见性
Node<E> next;
Node(E x) { item = x; }
}
//容量
private final int capacity;
//用原子变量,当前元素个数
private final AtomicInteger count = new AtomicInteger(0);
//头节点
private transient Node<E> head;
//表尾节点
private transient Node<E> last;
//获取元素或删除元素时,要加的takeLock锁
private final ReentrantLock takeLock = new ReentrantLock();
//获取元素时若队列为空,线程阻塞,直至notEmpty条件满足(被通知)
private final Condition notEmpty = takeLock.newCondition();
//插入元素时 要加putLock锁
private final ReentrantLock putLock = new ReentrantLock();
//插入时,若队列已满,线程阻塞,直至notFull条件满足(被通知)
private final Condition notFull = putLock.newCondition();
// 唤醒等待的take操作,插入数据时若插入前链表中无数据,则调用,表示链表不再为空
private void signalNotEmpty() {
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
notEmpty.signal();
} finally {
takeLock.unlock();
}
}
//唤醒等待插入操作,移除数据时若链表原先已满则调用,表示链表不再满
private void signalNotFull() {
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
notFull.signal();
} finally {
putLock.unlock();
}
}
// 插入到链表尾部
private void insert(E x) {
last = last.next = new Node<E>(x);
}
//获取并移除头元素
private E extract() {
Node<E> first = head.next;
head = first;
E x = first.item;
first.item = null;
return x;
}
//锁住两把锁,在remove,clear等方法中调用
private void fullyLock() {
putLock.lock();
takeLock.lock();
}
//和fullyLock成对使用
private void fullyUnlock() {
takeLock.unlock();
putLock.unlock();
}
//默认构造,容量为 Integer.MAX_VALUE public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
//指定容量的构造
public LinkedBlockingQueue(int capacity) {
if (capacity <= 0) throw new IllegalArgumentException();
this.capacity = capacity;
last = head = new Node<E>(null);
}
//指定初始化集合的构造
public LinkedBlockingQueue(Collection<? extends E> c) {
this(Integer.MAX_VALUE);
for (E e : c)
add(e);
}
//获得大小 public int size() {
return count.get();
}
//剩余容量
public int remainingCapacity() {
return capacity - count.get();
}
// 将指定元素插入到此队列的尾部,如已满,阻塞至队列中有元素被移除
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
//加put锁,多个线程不能同时进入
putLock.lockInterruptibly();
try {
try {
//容量已满,则一直阻塞
while (count.get() == capacity)
notFull.await();
} catch (InterruptedException ie) {
notFull.signal(); // propagate to a non-interrupted thread
throw ie;
}
//插入
insert(e);
c = count.getAndIncrement();
//通知链表未满
if (c + 1 < capacity)
notFull.signal();
} finally {
//解锁,注意必须在finally里调用,反正各种异常导致没有unlock使线程死锁
putLock.unlock();
}
//通知链表非空
if (c == 0)
signalNotEmpty();
}
// 将指定元素插入到此队列的尾部,如有必要,则等待一定时间以使空间变得可用。 public boolean offer(E e, long timeout, TimeUnit unit)
throws InterruptedException {
if (e == null) throw new NullPointerException();
long nanos = unit.toNanos(timeout);
int c = -1;
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
//加锁
putLock.lockInterruptibly();
try {
for (;;) {
//未满可插入
if (count.get() < capacity) {
insert(e);
c = count.getAndIncrement();
//通知未满
if (c + 1 < capacity)
notFull.signal();
//跳出循环
break;
}
//队列已满,未能插入,等待时间是负的,直接返回
if (nanos <= 0)
return false;
try {
//等待一定时间后再次尝试
nanos = notFull.awaitNanos(nanos);
} catch (InterruptedException ie) {
notFull.signal(); // propagate to a non-interrupted thread
throw ie;
}
}
} finally {
//解锁
putLock.unlock();
}
//通知已插入数据,链表非空
if (c == 0)
signalNotEmpty();
return true;
}
//将指定元素插入到此队列的尾部(如果立即可行且不会超出此队列的容量),
在成功时返回 true,如果此队列已满,则返回 false。 public boolean offer(E e) {
if (e == null) throw new NullPointerException();
final AtomicInteger count = this.count;
if (count.get() == capacity)
return false;
int c = -1;
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
//由于可能在lock被阻塞时其他线程进行了插入操作,需再次判断count
if (count.get() < capacity) {
insert(e);
c = count.getAndIncrement();
//通知未满
if (c + 1 < capacity)
notFull.signal();
}
} finally {
putLock.unlock();
}
//通知非空
if (c == 0)
signalNotEmpty();
// >0表示已成功插入
return c >= 0;
}
//获取并移除此队列的头部,若队列为空,则阻塞。
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
//加锁
takeLock.lockInterruptibly();
try {
try {
//队列为空时阻塞
while (count.get() == 0)
notEmpty.await();
} catch (InterruptedException ie) {
notEmpty.signal(); // propagate to a non-interrupted thread
throw ie;
}
//获取数据
x = extract();
c = count.getAndDecrement();
//通知非空
if (c > 1)
notEmpty.signal();
} finally {
takeLock.unlock();
}
//通知未满
if (c == capacity)
signalNotFull();
return x;
} //与offer方法结构基本一致,若队列为空,则阻塞一段时间,一段时间后仍为空,则返回null
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
E x = null;
int c = -1;
long nanos = unit.toNanos(timeout);
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
for (;;) {
if (count.get() > 0) {
x = extract();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
break;
}
if (nanos <= 0)
return null;
try {
nanos = notEmpty.awaitNanos(nanos);
} catch (InterruptedException ie) {
notEmpty.signal(); // propagate to a non-interrupted thread
throw ie;
}
}
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
} ////与offer方法结构基本一致 队列为空,不阻塞,直接返回null
public E poll() {
final AtomicInteger count = this.count;
if (count.get() == 0)
return null;
E x = null;
int c = -1;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
if (count.get() > 0) {
x = extract();
c = count.getAndDecrement();
if (c > 1)
notEmpty.signal();
}
} finally {
takeLock.unlock();
}
if (c == capacity)
signalNotFull();
return x;
}
//获取但不移除此队列的头;如果此队列为空,则返回 null。
public E peek() {
if (count.get() == 0)
return null;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
Node<E> first = head.next;
if (first == null)
return null;
else
return first.item;
} finally {
takeLock.unlock();
}
}
/**
* 从此队列移除指定元素的单个实例(如果存在)。
*/
public boolean remove(Object o) {
if (o == null) return false;
boolean removed = false;
//同时加锁,此时其他线程不能插入,不能移除
fullyLock();
try {
Node<E> trail = head;
Node<E> p = head.next;
//遍历,获取到该元素
while (p != null) {
if (o.equals(p.item)) {
removed = true;
break;
}
trail = p;
p = p.next;
}
//删除该元素
if (removed) {
p.item = null;
trail.next = p.next;
if (last == p)
last = trail;
if (count.getAndDecrement() == capacity)
notFull.signalAll();
}
} finally {
fullyUnlock();
}
return removed;
}
……
}

http://coderrobin.com/2015/02/12/java%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E5%9F%BA%E7%A1%80-ReentrantLock/

java并发编程基础-ReentrantLock及LinkedBlockingQueue源码分析的更多相关文章

  1. 【Java并发编程】16、ReentrantReadWriteLock源码分析

    一.前言 在分析了锁框架的其他类之后,下面进入锁框架中最后一个类ReentrantReadWriteLock的分析,它表示可重入读写锁,ReentrantReadWriteLock中包含了两种锁,读锁 ...

  2. 并发编程(九)—— Java 并发队列 BlockingQueue 实现之 LinkedBlockingQueue 源码分析

    LinkedBlockingQueue 在看源码之前,通过查询API发现对LinkedBlockingQueue特点的简单介绍: 1.LinkedBlockingQueue是一个由链表实现的有界队列阻 ...

  3. Java并发编程笔记之Semaphore信号量源码分析

    JUC 中 Semaphore 的使用与原理分析,Semaphore 也是 Java 中的一个同步器,与 CountDownLatch 和 CycleBarrier 不同在于它内部的计数器是递增的,那 ...

  4. 【Java并发编程】19、DelayQueue源码分析

    DelayQueue,带有延迟元素的线程安全队列,当非阻塞从队列中获取元素时,返回最早达到延迟时间的元素,或空(没有元素达到延迟时间).DelayQueue的泛型参数需要实现Delayed接口,Del ...

  5. 【Java并发编程】17、SynchronousQueue源码分析

    SynchronousQueue是一种特殊的阻塞队列,不同于LinkedBlockingQueue.ArrayBlockingQueue和PriorityBlockingQueue,其内部没有任何容量 ...

  6. 【Java并发编程】18、PriorityBlockingQueue源码分析

    PriorityBlockingQueue是一个基于数组实现的线程安全的无界队列,原理和内部结构跟PriorityQueue基本一样,只是多了个线程安全.javadoc里面提到一句,1:理论上是无界的 ...

  7. 并发编程(四)—— ThreadLocal源码分析及内存泄露预防

    今天我们一起探讨下ThreadLocal的实现原理和源码分析.首先,本文先谈一下对ThreadLocal的理解,然后根据ThreadLocal类的源码分析了其实现原理和使用需要注意的地方,最后给出了两 ...

  8. Java并发编程基础-ReentrantLock的机制

    同步锁: 我们知道,锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源,在Lock接口出现之前,Java应用程序只能依靠synchronized关键字来实现同步锁 ...

  9. 并发编程(八)—— Java 并发队列 BlockingQueue 实现之 ArrayBlockingQueue 源码分析

    开篇先介绍下 BlockingQueue 这个接口的规则,后面再看其实现. 阻塞队列概要 阻塞队列与我们平常接触的普通队列(LinkedList或ArrayList等)的最大不同点,在于阻塞队列的阻塞 ...

随机推荐

  1. POJ2728 Desert King

    一道生成树+\(0/1\)分数规划 原题链接 设每条边的距离为\(dis[x]\),两点高度差为\(h[x]\),该图的生成树为\(T\),则题目实际求的就是\(\dfrac{\sum\limits_ ...

  2. BZOJ 1977[BeiJing2010组队]次小生成树 Tree - 生成树

    描述: 就是求一个次小生成树的边权和 传送门 题解 我们先构造一个最小生成树, 把树上的边记录下来. 然后再枚举每条非树边(u, v, val),在树上找出u 到v 路径上的最小边$g_0$ 和 严格 ...

  3. Spring Environment(三)生命周期

    Spring Environment(三)生命周期 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring Envi ...

  4. PHP使用swoole来实现实时异步任务队列

    转载来自第七星尘的技术博客的<PHP使用swoole来实现实时异步任务队列> 关于异步任务队列 用户打开了我们的网站.他要做的就是勾选需要发邮件的代理商列表,然后把结算邮件发出去.假如我们 ...

  5. 如何利用Photoshop进行快速切图

    在UI设计中我们常常使用Ai来进行矢量图的绘制,然后导入Ps中进行设计.排版和导出. 在以前的版本中,切图一直是个很麻烦的事情,要么依托于脚本,要么手动一张张导出,很不方便,这种窘况在Photosho ...

  6. oralce的lag和lead函数

    https://www.cnblogs.com/always-online/p/5010185.html

  7. 使用Python完成表格自动输入

    看了看<Python编程快速上手>,写了个小脚本完成12306登录数据的自动输入.如下: 1 import webbrowser 2 import pyautogui 3 import t ...

  8. presentation skills

    下面是从一个网站摘录下来的关于presentation skill需要回答的14个问题:网站的地址为:http://www.mindtools.com/pages/article/newCS_96.h ...

  9. swift -inout关键字

    一般参数仅仅是在函数内可以改变的,当这个函数执行完后变量就会被销毁,不会有机会改变函数以外的变量,那么我们就会产生一个疑问,我们可不可以通过一个函数改变函数外面变量的值呢?答案是肯定的,这时我们就需要 ...

  10. springboot xml声明式事务管理方案

    在开发过程中springboot提供的常见的事务解决方案是使用注解方式实现. 使用注解 在启动类上添加注解 @EnableTransactionManagement 在需要事务控制的方法添加@Tran ...