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. BZOJ 1227 [SDOI2009]虔诚的墓主人 - 扫描线

    Solution 离散化 扫描线, 并用 $rest[i]$ 和 $cnt[i]$ 记录 第$i$列 总共有 $cnt[i]$棵常青树, 还有$rest[i]$ 没有被扫描到. 那么 第$i$ 列的方 ...

  2. 动态加载及Servlet容器加载

    动态加载 动态加载是 Servlet 3.0 中的新特性,它可以实现在不重启 Web 应用的情况下加载新的 Web 对象(Servlet.Filter.Listener). 为了实现动态加载的第一种方 ...

  3. Java 算法(一)贪心算法

    Java 算法(一)贪心算法 数据结构与算法目录(https://www.cnblogs.com/binarylei/p/10115867.html) 一.贪心算法 什么是贪心算法?是指在对问题进行求 ...

  4. Shortest Unsorted Continuous Subarray LT581

    Given an integer array, you need to find one continuous subarray that if you only sort this subarray ...

  5. js jquery 取得周月年时间

    function formatDate(date) { var myyear = date.getFullYear(); var mymonth = date.getMonth() + 1; var ...

  6. 2017/2/7utf-8与GBK的区别与修改

    1.GBK:是中文字符编码 2.UTF-8:是国际编码 3.使用GBK与UTF-8编码更耗内存,同时有英文字符多的 尽量用UTF-8编码 4.在项目中,几个修改字符串的方法:

  7. Mac 环境通用

    mac 下更新 .bash_profile 文件 1.打开terminal(终端) 2.cd ~ ( 进入当前用户的home目录) 3.open .bash_profile (打开.bash_prof ...

  8. shell 报错 /bin/bash^M: bad interpreter: No such file or directory

    shell 执行报错: ./test.sh: /bin/bash^M: bad interpreter: No such file or directory 原因:window和linux 的文件格式 ...

  9. thymeleaf使用基础教程

    thymeleaf 是新一代的模板引擎,在spring4.0中推荐使用thymeleaf来做前端模版引擎. thymeleaf介绍 简单说, Thymeleaf 是一个跟 Velocity.FreeM ...

  10. Cannot read property 'protocol' of undefined的原因和解决办法

    Cannot read property 'protocol' of undefined 原因:axios请求中的错误 1.请求地址写错了 2.没有引入http.js 3.引入http.js的时候,单 ...