一,Lock

二,关于锁的几个概念

三,ReentrantLock类图

四,几个重要的类

五,公平锁获取

5.1 lock

5.2 acquire

5.3 tryAcquire

    5.3.1 hasQueuedPredecessors

    5.3.2 compareAndSetState

    5.3.3 setExclusiveOwnerThread

    5.3.4 getExclusiveOwnerThread

5.4 addWaiter

5.5 acquireQueued

    5.5.1 shouldParkAfterFailedAcquire

    5.5.2 parkAndCheckInterrupt

5.6 selfInterrupt

Lock

Lock实现提供了比使用synchronized方法和synchronized语句块扩展性更好的锁操作,他们允许更灵活地构建,可以有相当不同的属性,并且可以支持多个相关的Condition对象。锁是一个控制被多个线程共享的资源访问的工具,一般来说,锁对共享资源提供排它地访问,一个时间内只能一个线程可以获取锁并且要求获取锁的线程是所有对共享资源的访问线种中的第一个。然而,一些锁可以允许对共享的资源并发访问,比如ReadWriteLock的读锁

关于锁的几个概念

锁重入
就是一个线程获取锁之后,这个线程还可以再次获取相同锁。
公平锁
获取锁的过程采用类似排队的机制,排在前面的线程先获取锁。
非公平锁
获取锁的过程,不排队,只要没有线程持有锁,就可以获取锁。

ReentrantLock类图

 

几个重要的类

从上图我们看到,
Lock是一个接口,他定义了关于锁的基本操作方法。
ReentrantLock实现了Lock接口,它是一个可重入的独占锁,与synchronized方法和语句块具有相同的行为和语义。
Sync是一个ReentrantLock中的抽象类。它和ReentrantLock是组合关系。
Sync继承于AbstractQueuedSynchronizer。
AbstractQueuedSynchronizer是一个抽象类。它提供了很多关于锁的基本操作。

Sync有两个子类。是NonfairSync和FairSync。分别表示非公平锁和公平锁的实现。
我们下面将会主要看实现的lock方法。
我们以ReentrantLock作为入口来看锁获取的过程。它常用的用法如下。
class X {
private final ReentrantLock lock = new ReentrantLock();
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}}
先看看ReentrantLock获取锁的过程
public void lock() {
sync.lock();
}

它直接调用的sync对象的lock()方法。

Sync类提供了公平锁和非公平锁的公共操作。它的lock方法是一个抽象方法。具体实现在公平锁
FairSync和非公平锁实现NonfairSync中实现。首先先看公平锁版本的实现

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

acquire(1)这方法是在在AbstractQueuedSynchronizer中实现

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

它大致做了如下几个事情。

1 tryAcquire() 就是试图获取锁,如果成功就结束

2 addWaiter 新建表示当前线程的一个节点。加入队列当中

3 acquireQueued 如果试图获取失败,就加入队列中等待

4 selfInterrupt(),自己给自己一个中断。

  • tryAcquire

这个方法在AbstractQueuedSynchronizer中是一个抽象的方法,具体实现就是子类中,这里就是在

ReentrantLock的FairSync类里面。
protected final boolean tryAcquire(int acquires) {
       1.获取当前线程
final Thread current = Thread.currentThread();
       2.当前state值
int c = getState();
if (c == 0) { //如果c等于0,表示当前没有线程占有锁,可以直接获取锁
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) { //如果队列中前面没有其它等待的线程,就把状态state值设置为acquires的值,这里是1.
setExclusiveOwnerThread(current); //把当前锁的持有线程设置为当前线程
return true;
}
}
else if (current == getExclusiveOwnerThread()) { //如果c不是0,说明已经有线程持有这个锁,就看持有锁的线程是不是当前线程,如果是就把state的值加1。然后更新它的值
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
  • hasQueuedPredecessors
public final boolean hasQueuedPredecessors() {
Node t = tail; // 队列的尾
Node h = head; // 队列的头
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}

这里主要的判断逻辑,就是如果头和尾不相等,说明这个队列就不为空,并且队列的头的下一下节点不是当前线程,就说明队列中有前继节点。

但是我第一次看这个代码的时候,有个疑问,为什么头节点的next为null的时候,也说明有前继节点呢?

  • compareAndSetState

这个一个CAS操作,就是原子地设置state的值,如果期望值是0,就设置state的值为传入的值。

  • setExclusiveOwnerThread
这个方法是设置当前锁的持有线程为当前线程,这个方法是在AbstractOwnableSynchronizer抽象类里定义的,它是一个表示这一个锁是由那个线程独占持有的辅助类。
AbstractQueuedSynchronizer类继承了这个类。

  • getExclusiveOwnerThread
如果试图获取锁不成功,就进行下一步,首先就是把这个线程加入到队列中,
addWaiter
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode); //首先创建一个表示当前线程的节点
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) { //首先如果队列的尾节点不为null,就直接把新加节点加入到尾节点后面,把新加入节点设置为尾节点,
node.prev = pred;//把新加入的节点的前继节点指向尾节点
if (compareAndSetTail(pred, node)) {//设置新加入的节点为尾节点
pred.next = node;//之前的尾结点的后继节点指向新加入的节点
return node;//返回新加入的节点
}
}
enq(node); 如果尾结为空,就调用这个方法新增节点
return node;
}
这个方法的入参是Node.EXCLUSIVE。表示这个节点正在以排它模式等待,也就是说锁是排它锁。
注意上面的是先设置一个节点的prev节点,然后设置节点的next节点。所以在上面判断前面是否有等待的节点时,如果next为null时也认为是有等待的节点,可能判断的时候,正好有一个节点正在入队。还没设置
prev的next节点的值。我是这么理解的,
private Node enq(final Node node) {
for (;;) {//自旋,直到成功把节点加入到队列中
Node t = tail;
if (t == null) { //如果尾节点为null,说明这个队列还没有初始化
if (compareAndSetHead(new Node())) //new一个节点,并设置为这个节点为头节点,注意头节点是一个占位节点,它是一个空节点。
tail = head;
} else {//如果头节点不为空
node.prev = t;//把当前节点的前继设置为尾节点
if (compareAndSetTail(t, node)) {//把当前节点设置为尾节点
t.next = node;//把原来尾节点的后继设置为当前节点
return t;
}
}
}
}
当先节点加入到队列中,就调用下面的方法,它是
  • acquireQueued
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;//是否中断过标示,默认是没有
for (;;) {//自旋,直到这个线程获取到锁
final Node p = node.predecessor(); //当前节点的前继节点
if (p == head && tryAcquire(arg)) {//如果前继节点是头节点,就再次尝试获取锁
setHead(node);//如果获取锁成功,就把当前线程的节点设置为头节点
p.next = null; // 把原来的头节点的next节点置为null。为了更快的垃圾回收掉。
failed = false;
return interrupted;//返回是否中断过的标示
}
          //如果获取失败,就判断是否需要阻塞当前线程,直到下一个成功获取锁的线程的唤醒。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

  • shouldParkAfterFailedAcquire,判断在获取锁失败后,是否需要阻塞当前线程
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus; //前继结点的waitStatus值
if (ws == Node.SIGNAL)//如果值是Node.SIGNAL,表示前一个节点被释放后,就会唤醒这个线程,所以这个线程可以阻塞,所以返回true。
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {//如果值大于0,说明是被取消了。然后就往前找,直到找到一个不是被取消的前继节点
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}

  • parkAndCheckInterrupt
如果需要阻塞 就调用这个方法阻塞当前线程。并返回当前线程是否被中断过。如果需要阻塞并且被中断过。就设置中断状态为true。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}


  • selfInterrupt

如果尝试获取失败,在队列中等待获取锁时被中断过。就调用这个方法给自己一个中断

static void selfInterrupt() {
Thread.currentThread().interrupt();
}
二 非公平锁获取
为什么是非公平呢,是因为获取锁的时候。不会去排队,如果没有线程拥有锁。就可以获取锁成功。如果不成功。他其实还是会和公平锁一样来获取锁。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}

1首先调用 compareAndSetState来设置状态的值。如果成功。就说明获取到锁,并设置当前线程为拥有锁的线程。

否则就就和公平锁的获取方式一样了。

三 锁释放


Lock接口里定义了unlock方法。

ReentrantLock 的unlock方法,他调用sync的release方法。它是在AbstractQueuedSynchronizer类里实现。

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


public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}

 

Java锁及AbstractQueuedSynchronizer源码分析的更多相关文章

  1. java中的锁之AbstractQueuedSynchronizer源码分析(一)

    一.AbstractQueuedSynchronizer类介绍. 该抽象类有两个内部类,分别是静态不可继承的Node类和公有的ConditionObject类.AbstractQueuedSynchr ...

  2. java中的锁之AbstractQueuedSynchronizer源码分析(二)

    一.成员变量. 1.目录. 2.state.该变量标记为volatile,说明该变量是对所有线程可见的.作用在于每个线程改变该值,都会马上让其他线程可见,在CAS(可见锁概念与锁优化)的时候是必不可少 ...

  3. Java并发编程-AbstractQueuedSynchronizer源码分析

    简介 提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架.该同步器(以下简称同步器)利用了一个int来表示状态,期望它能够成为实现大部分同步需求的基础.使用的方法是继承,子类通过 ...

  4. Java并发系列[2]----AbstractQueuedSynchronizer源码分析之独占模式

    在上一篇<Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析>中我们介绍了AbstractQueuedSynchronizer基本的一些概 ...

  5. Java并发系列[3]----AbstractQueuedSynchronizer源码分析之共享模式

    通过上一篇的分析,我们知道了独占模式获取锁有三种方式,分别是不响应线程中断获取,响应线程中断获取,设置超时时间获取.在共享模式下获取锁的方式也是这三种,而且基本上都是大同小异,我们搞清楚了一种就能很快 ...

  6. Java并发系列[4]----AbstractQueuedSynchronizer源码分析之条件队列

    通过前面三篇的分析,我们深入了解了AbstractQueuedSynchronizer的内部结构和一些设计理念,知道了AbstractQueuedSynchronizer内部维护了一个同步状态和两个排 ...

  7. AbstractQueuedSynchronizer源码分析

    AbstractQueuedSynchronizer源码分析 前提 AQS(java.util.concurrent.locks.AbstractQueuedSynchronizer)是并发编程大师D ...

  8. Java并发指南10:Java 读写锁 ReentrantReadWriteLock 源码分析

    Java 读写锁 ReentrantReadWriteLock 源码分析 转自:https://www.javadoop.com/post/reentrant-read-write-lock#toc5 ...

  9. 死磕 java集合之DelayQueue源码分析

    问题 (1)DelayQueue是阻塞队列吗? (2)DelayQueue的实现方式? (3)DelayQueue主要用于什么场景? 简介 DelayQueue是java并发包下的延时阻塞队列,常用于 ...

随机推荐

  1. Java编程题每日一练day1

    Day1共7题 package com.pcx.day1; /** * 设一个字符数组,对其元音字母进行统计 * a e i o u * @author Administrator * */ publ ...

  2. HDU 1170 Shopping Offers 离散+状态压缩+完全背包

    题目链接: http://poj.org/problem?id=1170 Shopping Offers Time Limit: 1000MSMemory Limit: 10000K 问题描述 In ...

  3. 13_Java面向对象_第13天(static、final、匿名对象、内部类、包、修饰符、代码块)_讲义

    今日内容介绍 1.final 关键字 2.static 关键字 3.匿名对象 4.内部类 5.包的声明与访问 6.访问修饰符 7.代码块 01final关键字概念 A: 概述 继承的出现提高了代码的复 ...

  4. 关于js typeof 的理解

  5. Linux上两种网络连接方式

    模式一:NAT方式好处:路由器更换,或者交换机更换,网络仍然可以使用,所用使用最多 准备工作: 查看VMware服务器启动情况,五个全开模式 vmnet8开启模式 1 配置VMware交换机的ip地址 ...

  6. windows多线程(八) 信号量Semaphore

    如果你看到了这里,我就认为你已经对掌握了有关关键段 CriticalSection.互斥量Mutex和事件Event有关的内容,所以最基本的东西就不再介绍了.如果没有掌握上面说的内容,可以看这里: 关 ...

  7. java中为什么要进行对象序列化?

    序列化其实很好理解,假如你现在做一个项目,项目是分工合作的,并且你喝其他小组成员不在同一个城市,那么你要如何把你写的那些类给其他小组成员呢?这个时候就要用到序列化了,简单的说:序列化就是将内存中的类或 ...

  8. Java词频统计

    public class WordCount { public static void main(String[] args) { String[] stopWords = { "" ...

  9. 多进程编程之守护进程Daemonize

    1.守护进程 守护进程(daemon)是一类在后台运行的特殊进程,用于执行特定的系统任务.很多守护进程在系统引导的时候启动,并且一直运行直到系统关闭.另一些只在需要的时候才启动,完成任务后就自动结束. ...

  10. 函数防抖与函数节流 封装好的debounce和throttle函数

    /** * 空闲控制 返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行 * * @param {function} func 传入函数,最后一个参数是额外增加的this对象,. ...