您的位置:首页 > 编程语言 > Java开发

java.util.concurrent源码分析(三)ReentrantLock实现

2018-03-26 20:13 731 查看

1、ReentrantLock介绍

(1)简介

java.util.concurrent.locks
包中有很多Lock的实现类, 常用的有
ReentrantLock, ReadWriteLock
(实现类
ReentrantReadWriteLock
). 这些锁的实现思路都大同小异, 都依赖
java.util.concurrent.AbstractQueuedSynchronizer
类。

(2)内部结构

ReentrantLock-->Lock
内部类NonfairSync/FairSync-->内部类Sync-->AbstractQueuedSynchronizer-->AbstractOwnableSynchronizer
注:-->表示继承或实现


(3)可重入锁

ReadWriteLock是一个可重入锁:当前持有该锁的线程能够多次获取该锁,无需等待,可避免死锁。synchronized实现的锁也是可重入锁。
public class Test {
ReentrantLock lock = new ReentrantLock();
public void get() {
lock.lock();
//do someThings
set();
lock.unlock();
}
public void set() {
lock.lock();
//do someThings
lock.unlock();
}
}
//调用get()时,如果获取了lock锁,get()再调用set()时可直接获取lock,无需等待。


(4)公平锁和非公平锁

公平锁:线程获取锁的顺序和调用lock的顺序一样,FIFO;
非公平锁:线程获取锁的顺序和调用lock的顺序无关,全凭运气:两个线程进行锁交替时(此时锁空闲)被第三个线程强行抢到了;


2、Lock接口

Lock接口,是对控制并发的工具的抽象。它比使用
synchronized
关键词更灵活,并且能够支持条件变量。它是一种控制并发的工具,一般来说,同一时间内只有一个线程可以获取这个锁并占用资源。其他线程想要获取锁,必须等待这个线程释放锁。在Java实现中的ReentrantLock就是这样的锁。另外一种锁,它可以允许多个线程读取资源,但是只能允许一个线程写入资源,ReadWriteLock就是这样一种特殊的锁,简称读写锁。下面是对Lock接口的几个方法的总体描述:

(1)lock()

获取锁,如果锁无法获取,那么当前的线程就变为不可被调度,直到锁被获取到。


(2)lockInterruptibly()

获取锁,除非当前线程被中断。如果获取到了锁,那么立即返回;
如果获取不到,那么当前线程变得不可被调度,一直休眠直到下面两件事情发生:
a.当前线程获取到了锁;
b.其他的线程中断了当前的线程;


(3)tryLock()

如果调用的时候能够获取锁,那么就获取锁并且返回true,如果当前的锁无法获取到,那么这个方法会立刻返回false。


(4)tryLcok(long time,TimeUnit unit)

在指定时间内尝试获取锁如果可以获取锁,那么获取锁并且返回true;
如果当前的锁无法获取,那么当前的线程变得不可被调度,直到下面三件事之一发生:
a.当前线程获取到了锁;
b.当前线程被其他线程中断;
c.指定的等待时间到了;


(5)unlock()

释放当前线程占用的锁。


3、AQS

JCU包里面几乎所有的有关锁、多线程并发以及线程同步器等重要组件的实现都是基于AQS这个框架。AQS的核心思想是基于
volatile int state;
这样的一个属性同时配合Unsafe工具对其原子性的操作来实现对当前锁的状态进行修改。当state的值为0的时候,标识改Lock不被任何线程所占有。

4、ReentrantLock的创建

(1)ReentrantLock支持两种锁, 公平锁和非公平锁

public ReentrantLock() {
sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
//可知默认创建的是非公平锁


(2)三个内部类

/**
* 该锁同步控制的一个基类
*/
static abstract class Sync extends AbstractQueuedSynchronizer

/**
* 非公平锁同步器
*/
final static class NonfairSync extends Sync

/**
* 公平锁同步器
*/
final static class FairSync extends Sync


(3)ReentrantLock.lock()

public void lock() {
sync.lock();
}
//也就是说由内部的同步器来决定


(4)NonfairSync.lock()

final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}

public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}


流程:

1)首先基于CAS将state(锁数量)从0设置为1, 如果设置成功, 设置当前线程为独占锁的线程;(此处就是非公平锁的第一次插队).
2)如果设置失败(即当前的锁数量可能已经为1了), 这个时候当前线程执行AQS的acquire(1)方法;
2.1)acquire(1)方法首先调用下边的tryAcquire(1)方法, 如果当前线程是等待队列中的头节点则获取成功, 则返回.如果失败, 继续执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法.
2.2)在执行acquireQueued方法之前,会先执行addWaiter方法,将当前线程加入到等待获取锁的队列的尾部.
2.2.1)addWaiter(Node.EXCLUSIVE)将当前线程封装进Node节点node,然后将该节点加入等待队列(先快速入队, 如果快速入队不成功, 其使用正常入队方法无限循环一直到Node节点入队为止).
2.2.1.1)快速入队: 如果同步等待队列存在尾节点, 将使用CAS尝试将尾节点设置为node, 并将之前的尾节点插入到node之前.
2.2.1.2)正常入队: 如果同步等待队列不存在尾节点或者上述CAS尝试不成功的话, 就执行正常入队(该方法是一个无限循环的过程, 即直到入队为止). 如果尾节点为空(初始化同步等待队列), 创建一个dummy节点, 并将该节点通过CAS尝试设置到头节点上去, 设置成功的话, 将尾节点也指向该dummy节点(即头节点和尾节点都指向该dummy节点). 如果尾节点不为空, 执行与快速入队相同的逻辑, 即使用CAS尝试将尾节点设置为node, 并将之前的尾节点插入到node之前.
2.2.2)node节点入队之后, 就去执行acquireQueued(final Node node, int arg)(此处会出现阻塞).
2.2.2.1)获取node的前驱节点p, 如果p是头节点, 就继续使用tryAcquire(1)方法去尝试请求成功. 如果第一次请求就成功, interrupted=false. 如果是之后的循环中将线程挂起之后被其他线程唤醒, interrupted=true. (注意p==head&&tryAcquire(1)成功是唯一跳出循环的方法).
2.2.2.2)如果p不是头节点, 或者tryAcquire(1)请求不成功,就去执行shouldParkAfterFailedAcquire(Node pred, Node node)来检测当前节点是不是可以安全的被挂起. 是, 则调用parkAndCheckInterrupt方法挂起当前线程. 否, 则继续执行2.2.2.1(这是一个死循环).
2.2.2.3)线程调用parkAndCheckInterrupt方法后会进入阻塞状态,等待其他线程唤醒. 唤醒后会将interrupted置为true, 并且继续执行2.2.2.1.
2.2.2.4)若在这个循环中产生了异常, 我们就会执行cancelAcquire(Node node)取消node的获取锁的意图.


(5)FairSync.lock()

final void lock() {
acquire(1);
}


(6)NonfairSync、FairSync.unlock()

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


流程:

1)获取当前的锁数量state, 然后用这个锁数量减去解锁的数量(这里为1), 最后得出结果c;
2)判断当前线程是不是独占锁的线程, 如果不是, 抛出异常;
3)若c==0,锁释放成功,返回true;
4)若c!=0,锁释放失败,锁数量置为c, 返回false;
5)如果锁被释放成功的话, 唤醒距离头节点最近的一个非取消的节点;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: