线程同步-ReentrantLock&AQS

一、JAVA并发概述

  JAVA开发中常见的并发问题有线程同步、线程协作、线程分工等。JUI包(java.util.concurrent)中提供了多种API和工具类来解决各种并发问题。这几类并发问题往往不是独立存在的,开发者可根据实际问题决定采用哪种方式。本文主要介绍线程同步中的Lock&AQS原理。

  线程同步是指:当有一个线程在对临界资源进行操作时,其他线程都不可以操作该临界资源,直到该线程完成操作,其他线程才能继续操作。实现线程同步的方法有:加锁和无锁。

加锁无锁
含义当一个线程访问临界资源时,会对临界资源加锁,当其他线程访问时会被阻塞。
(实际实现中考虑到效率等原因,一般不会立即阻塞,而是先进行自旋等操作)
当一个线程访问临界资源时,如果该资源正在被其他线程访问,则该线程会访问失败并返回false或者自旋直至成功。
实现方式synchronized、LockCAS、Atomic原子类等

  如上所示,JAVA开发中常用的加锁方式有两种:synchronized关键字、Lock。

// Lock接口中定义了加锁/释放锁的方法;
// Lock的实现类有ReentrantLock、ReentrantReadWriteLock.ReadLock、ReentrantReadWriteLock.WriteLock等
public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

  下面继续介绍synchronized、Lock加锁的用法。

二、ReentrantLock

2.1 基本用法

  ReentrantLock实现了Lock接口。ReentrantLock即可重入锁,指一个线程(已经获得锁)可以对临界资源重复加锁。对比synchronized的使用,ReentrantLock使用方式如下:

// 1.synchronized使用方式
public synchronized void testLock1() {
    doSomething();
}

// 2.ReentrantLock使用方式
public void testLock2() {
    ReentrantLock lock = new ReentrantLock();
    lock.lock();
    try {
        doSomething();
    } finally {
        lock.unlock();
    }
}

  可以看到,相比synchronized方式,ReentrantLock在加锁和释放锁时都需要在代码中显式调用,而synchronized方式会在方法执行前自动加锁,方法执行结束后自动释放锁。ReentrantLock的优点在于ReentrantLock提供了多种获取锁的方式(尝试获取锁、超时获取锁、可中断锁等)、支持公平锁/非公平锁、可结合Condition关联多个条件队列。

ReentrantLocksynchronized
使用方式需要手动加锁/释放锁使用结束后自动释放锁
锁类型支持公平锁/非公平锁非公平锁
获取锁方式提供了多种获取锁的方式
1.尝试获取锁:获取锁失败时会立即返回false;
2.超时获取锁:获取锁时可设置超时时间
3.响应中断:线程可以在被阻塞时响应中断
获取锁失败时会阻塞当前线程,直到获取锁成功
条件队列可创建多个Condition实现关联多个条件队列。
通过Condition.await()、Condition.signal()实现。
只有一个条件队列。
通过Object.wait()、Object.notify()实现。
锁实现机制依赖AQS依赖Monitor机制(每一个Java对象都有一把看不见的锁);
锁升级机制;
Monitor依赖于底层操作系统的Mutex Lock(互斥锁)来实现线程同步;

2.2 ReentrantLock源码分析

  下面从ReentrantLock的构造函数,以及ReentrantLock.lock()方法来分析加锁过程。如下所示,创建ReentrantLock对象时支持公平锁/非公平锁(默认采用非公平锁),ReentrantLock.lock()的实现依赖其内部类ReentrantLock.Sync:

public class ReentrantLock implements Lock {

    private final Sync sync;

    public ReentrantLock() { 
        // 默认是非公平锁
        sync = new NonfairSync(); 
    }

    public ReentrantLock(boolean fair) {
        // 支持公平锁/非公平锁
        sync = fair ? new FairSync() : new NonfairSync();
    }

    public void lock() { sync.lock(); }
}

2.3 非公平锁&公平锁

  内部类ReentrantLock.Sync有两个子类:NonfairSync、FairSync,对应着非公平锁、公平锁。二者的区别在于:获取锁的顺序是否公平,是否与请求获取锁的顺序一样。分两种情况讨论:

  1. 情况1:当新的节点请求获取锁时,锁正在被持有,非公平锁与公平锁都会把新节点放到等待队列;
  2. 情况2:当新的节点请求获取锁时,锁刚好被释放,非公平锁会先尝试获取锁,获取失败才会进入等待队列。

流程图-202306212013.png

  从源码上看,非公平锁NonfairSync和公平锁FairSync的代码区别主要在于:

  1. 在加锁方法lock()中,非公平锁NonfairSync会先尝试CAS更新state状态值,更新成功则直接获得锁;
  2. 在尝试获取锁方法tryAcquire()中,非公平锁NonfairSync会再次先尝试CAS更新state状态值,更新成功则直接获得锁;而公平锁FairSync会多一个hasQueuedPredecessors()判断,判断是否有其他线程等候了更长时间。
public class ReentrantLock implements Lock {
    /**
     * 非公平锁
     */
    static final class NonfairSync extends Sync {

        /**
         * 获取锁
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

        /**
         * 尝试获取锁;
         * 源码中nonfairTryAcquire()在其父类中ReentrantLock.Sync,为方便对比放到这里
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return 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;
        }
    }

    /**
     * 公平锁
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

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

        /**
         * 尝试获取锁
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                // 相比非公平锁,这里多了一个判断:hasQueuedPredecessors()
                // hasQueuedPredecessors():判断等待获取锁的队列中,是否有其它等待时间更长的线程
                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;
        }
    }
}

2.4 ReentrantLock.Sync

  上面提到的非公平锁NonfairSync、公平锁FairSync,都是ReentrantLock.Sync的子类。

public class ReentrantLock implements Lock {

    abstract static class Sync extends AbstractQueuedSynchronizer {

        abstract void lock();

        /**
         * 尝试释放锁
         */
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
    }
}

  可以看到,ReentrantLock.Sync、ReentrantLock.NonfairSync、ReentrantLock.FairSync只是实现了加锁/释放锁的主体逻辑,而具体的工作如修改同步状态、等待队列、线程阻塞都是在其父类AbstractQueuedSynchronizer中实现的。即AbstractQueuedSynchronizer提供了一个同步框架,其子类在这套同步框架的基础上实现锁功能。

2.5 总结

  综合上面的分析,AQS提供了一个基础的同步框架,ReentrantLock在AQS的基础上实现了加锁/解锁的功能。ReentrantLock、ReentrantLock.NonfairSync、ReentrantLock.FairSync、AQS的关系如下:
流程图-202306212011.png

三、AQS源码分析

3.1 AQS基本结构

  AQS即AbstractQueuedSynchronizer,AQS提供了一个基于FIFO等待队列的同步框架。AQS的子类在这套框架基础上实现AQS的几个protect方法来实现同步锁,例如ReentrantLock.Sync、ReentrantReadWriteLock.Sync、CountDownLatch.Sync、Semaphore.Sync等。

  AQS使用volatile int类型的state表示同步状态,用CAS修改state值,用FIFO队列管理获取临界资源的线程。如下所示AQS的FIFO队列持有head节点和tail节点:

public abstract class AbstractQueuedSynchronizer {
    /** 队列头节点 */
    private transient volatile Node head;
    /** 队列尾节点 */
    private transient volatile Node tail;
    /** 同步状态 */
    private volatile int state;
    private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();

    /**
     * CAS设置state;用于尝试获取锁/尝试释放锁操作
     */
    protected final boolean compareAndSetState(int expect, int update) {
        return U.compareAndSwapInt(this, STATE, expect, update);
    }
}

3.2 Node定义

  其中,Node是把单个线程封装成的AQS内部类,每个Node持有前后节点的引用:

public abstract class AbstractQueuedSynchronizer {
    static final class Node {
        /**
         * 当前节点的状态,包括SIGNAL 、CANCELLED 、CONDITION、PROPAGATE、0
         */
        volatile int waitStatus;
        /** 前一个节点 */
        volatile Node prev;
        /** 下一个节点 */
        volatile Node next;
        /** 指向的线程 */
        volatile Thread thread;
        /** Condition下的下一个节点;或SHARED节点 */
        Node nextWaiter;
        private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();

        /**
         * CAS设置当前节点状态
         */
        final boolean compareAndSetWaitStatus(int expect, int update) {
            return U.compareAndSwapInt(this, WAITSTATUS, expect, update);
        }

        /**
         * CAS设置下一个节点
         */
        final boolean compareAndSetNext(Node expect, Node update) {
            return U.compareAndSwapObject(this, NEXT, expect, update);
        }
    }
}

3.3 lock获取锁过程

  以为非公平锁为例分析ReentrantLock.lock()获取锁流程源码:

public class ReentrantLock implements Lock {
    /**
     * 非公平锁
     */
    static final class NonfairSync extends Sync {

        /**
         * 获取锁
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                // 1.尝试CAS设置state值,设置成功,则获取锁
                setExclusiveOwnerThread(Thread.currentThread());
            else
                // 2.CAS设置失败,则执行acquire()进入获取锁流程
                acquire(1);
        }

        /**
         * 尝试获取锁;
         */
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

        /**
         * 非公平方式尝试获取锁;
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                // 同上,这个时候有可能其他线程已经释放了锁,所以再次尝试CAS获取锁
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            } else if (current == getExclusiveOwnerThread()) {
                // 当前线程已经是获取锁的线程,则更新state的值,获取锁成功,即可重入锁
                int nextc = c + acquires;
                setState(nextc);
                return true;
            }
            return false;
        }
    }
}

/**
 * 基于FIFO等待队列的同步框架
 */
public abstract class AbstractQueuedSynchronizer {

    /**
     * 获取锁
     */
    public final void acquire(int arg) {
        // 1.tryAcquire()尝试获取锁;获取成功则返回;
        // 2.如果获取失败,则执行addWaiter()创建Node节点(引用着当前线程)并加入同步队列
        // 3.加入同步队列后,会执行acquireQueued()再次尝试获取锁(因为这时其他线程可能已经释放锁)
        // 4.仍然获取失败,则调用LockSupport.park(this)阻塞当前线程;
        if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            // 如果阻塞的过程被中断(超时或者其他原因),则会执行interrupt()
            selfInterrupt();
    }

    /**
     * 把Node节点添加到同步队列
     */
    private Node addWaiter(Node mode) {
        Node node = new Node(mode);
        for (;;) {
            Node oldTail = tail;
            if (oldTail != null) {
                // 1.用Unsafe把当前节点的前一个节点设置为同步队列的尾节点
                U.putObject(node, Node.PREV, oldTail);
                // 2.用Unsafe更新同步队列的尾节点为当前节点
                if (compareAndSetTail(oldTail, node)) {
                    // 3.旧尾节点的next节点更新为当前节点
                    oldTail.next = node;
                    return node;
                }
            } else {
                // 初始化同步队列,即用Unsafe设置队列HEAD值;
                // HEAD节点是空节点,创建完HEAD节点后继续for循环把当前节点添加到队列
                initializeSyncQueue();
            }
        }
    }

    /**
     * 已经在队列中的节点获取锁
     */
    final boolean acquireQueued(final Node node, int arg) {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            // 如果当前节点的前节点即HEAD节点(HEAD是空节点),说明该节点是同步队列第一个节点,则再次尝试获取锁
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null;
                return interrupted;
            }
            // 获取失败后判断是否阻塞线程,如果是则继续执行parkAndCheckInterrupt()
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                interrupted = true;
        }
    }

    /**
     * 阻塞当前线程
     */
    private final boolean parkAndCheckInterrupt() {
        // 阻塞线程
        LockSupport.park(this);
        // 当线程恢复执行后,返回是否中断线程
        return Thread.interrupted();
    }
}

3.4 unlock释放锁过程

  下面分析ReentrantLock.unlock()释放锁的流程源码:

public class ReentrantLock implements Lock {

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

    abstract static class Sync extends AbstractQueuedSynchronizer {

        /**
         * 释放锁
         */
        protected final boolean tryRelease(int releases) {
            // 1.计算释放锁后的state值(state值即同步状态)
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                // 2.如果值为0,则设置持有锁的线程为null
                setExclusiveOwnerThread(null);
            }
            // 3.更新state值
            setState(c);
            return free;
        }
    }
}

/**
 * 基于FIFO等待队列的同步框架
 */
public abstract class AbstractQueuedSynchronizer {

    /**
     * 释放锁
     */
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                // 释放锁后,唤醒等待线程
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

    /**
     * 释放锁后,唤醒等待线程
     */
    private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            node.compareAndSetWaitStatus(ws, 0);
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node p = tail; p != node && p != null; p = p.prev)
                if (p.waitStatus <= 0)
                    s = p;
        }
        if (s != null)
            // 唤醒线程
            LockSupport.unpark(s.thread);
    }
}

3.5 总结

  AQS提供了基于FIFO等待队列的同步框架,包括同步状态state、同步队列、线程阻塞、互斥锁/共享锁等基础功能。ReentrantLock在AQS同步框架的基础上,实现了获取锁/释放锁的具体逻辑(例如非公平锁/公平锁的获取锁流程等);ReentrantReadWriteLock在AQS同步框架的互斥锁/共享锁基础上,实现了读写锁的功能(写锁WriteLock是互斥锁,而读锁ReadLock是共享锁)。开发者也可以在AQS的基础上自定义其他锁实现。
流程图-202306231622.png

The End

欢迎关注我,一起解锁更多技能:BC的掘金主页~💐 BC的CSDN主页~💐💐
请添加图片描述

JVM结构官方文档:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.3

英文参考文档:http://tutorials.jenkov.com/java-concurrency/index.html

concurrent工具包参考文档:http://tutorials.jenkov.com/java-util-concurrent/index.html

锁类型:https://tech.meituan.com/2018/11/15/java-lock.html

AQS:https://tech.meituan.com/2019/12/05/aqs-theory-and-apply.html

JAVA并发参考文档:http://concurrent.redspider.group/article/01/1.html

线程池参考文档:https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值