【并发编程】一张图彻底理解了ReentrantLock和AQS

目录

1.引言

1.1 类比synchronized

2.使用

2.1 同步

2.2 通信协同

3.实现

2.1 类图

2.2 核心方法

2.2.1 构造

2.2.2 lock

2.3 AQS

2.3.1 字段分析

2.3.2 lock()方法

2.3.3 release(int)


1.引言

在Java多线程中,可以使用synchronized关键字来实现线程之间同步互斥,但在JDK1.5中新增加了ReentrantLock类也能达到同样的效果,并且在扩展功能上也更加强大,比如具有嗅探锁定、多路分支通知等功能,而且在使用上也比synchronized更加的灵活。

类ReentrantLock具有完全互斥排他的效果,即同一时间只有一个线程在执行ReentrantLock.lock()方法后面的任务。虽然保证了实例变量的线程安全性,但效率却是非常低下的

1.1 类比synchronized

  synchronizedReentrantLock
相同点

可重入

比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的

不同点实现JVM实现

JDK API (java.util.concurrent包

使用

使用简单

通信机制配合

synchronized与wait()和notify()/notifyAll()

使用略复杂

需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成

通信配合

Condition await和signal

功能

功能简单单一

不可中断

仅支持非公平锁

高级功能

等待可中断; lock.lockInterruptibly()等待的线程可选择放弃等待

可实现公平锁;(公平锁就是先等待的线程先获得锁默认情况是非公平,ReentrantLock(boolean fair)构造方法

可实现选择性通知(锁可以绑定多个条件)ReentrantLockCondition实例可以实现“选择性通知” 

2.使用

2.1 同步

public static void main(String[] args) throws InterruptedException {
    Lock lock = new ReentrantLock(); // 3个线程竞争的锁
    // 启动线程1
    new Thread(()->{
        try {
            lock.lock();
            Thread.sleep(20000); // 休眠20s后释放锁(sleep锁不释放)
            lock.unlock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, "thread1").start();
    Thread.sleep(5000); // 主线程休眠5s
    // 启动线程2
    new Thread(()->{
        try {
            lock.lock();
            Thread.sleep(30000); // 休眠30s后释放锁(sleep锁不释放)
            lock.unlock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, "thread2").start();
    Thread.sleep(5000); // 主线程休眠5s
    // 启动线程3
    new Thread(()->{
        try {
            lock.lock();
            Thread.sleep(30000); // 休眠30s后释放锁(sleep锁不释放)
            lock.unlock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, "thread3").start();
}

2.2 通信协同

配合try finally,可以搭配多个condition

public class ConditionUseCase {
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    public void conditionWait() throws InterruptedException {
        lock.lock();
        try {
            condition.await(); // 条件不满足时等待
        } finally {
            lock.unlock();
        }
    }

    public void conditionSignal() throws InterruptedException {
        lock.lock();
        try {
            condition.signal(); // 条件满足时唤醒
        } finally {
            lock.unlock();
        }
    }
}

3.实现

2.1 类图

从类图可以看出底层基于AQS实现,ReentrantLock的lock等方法,委托给其依赖sync的lock方法

2.2 核心方法

2.2.1 构造

默认无参构造使用非公平锁实现,可以传入参数选择使用公平锁。

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

2.2.2 lock

依赖sync的实现,常用非公平锁的实现,如下图Sync继承自AQS(AbstractQueuedSynchronizer, 队列同步器),先利用CAS(state, 0, 1)操作(类似于synchronized偏向锁优化),下一节会详细介绍AQS实现

2.3 AQS

AQS核心思想是,如果被请求的共享资源空闲,则将当前线程设置为有效的工作线程,并且将共享资源设置为锁定状态state

如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,将暂时获取不到锁的线程加入到队列中。

2.3.1 字段分析

  1. state 锁状态
  2. exclusiveOwnerThread 持有锁的线程
  3. head、tail 链表 等待队列
    1. 链表节点 等待状态waitStatus
      1. 0,初始值,通过CAS被修改
      2. CANCELLED=1,  中断取消,不再等待锁了,大于0表示已经没必要被通知了
      3. SIGNAL=-1,      信号,通知后继节点,我释放或者中断放弃了
      4. CONDITION=-2,  当前线程等待在Condition上
      5. PROPAGATE=-3,  传播 头节点在共享锁释放时
    2. 前驱后继节点
    3. 等待的线程

等待队列节点类Node,等待队列是“ CLH”(Craig,Landin和 Hagersten)锁定队列的变体,CLH锁通常用于自旋锁。

每个节点中的“状态waitStatus”字段跟踪线程是否应阻塞
节点的前驱节点释放时会signal后面的节点,每个节点都监听该信号signalAll(),单个等待线程signal()

头是哨兵节点

2.3.2 lock()方法

lock方法的时序如下:

// NonfairSync 利用CAS synchronized偏向锁类似
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();
    }
lockacquire
方法解析

1.addWaiter

Node node = new Node(Thread.currentThread(), mode);

compareAndSetTail(pred, node)

当前线程创建新的节点,插入到队尾

2.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; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed) cancelAcquire(node);
    }}

排队获取锁,

for循环自旋获取,如果阻塞了,循环也暂停,如果不阻塞继续获取,获取到了return

 

仅当前驱节点是哨兵头,才去获取锁CAS

此处获取不成功,由于锁被线程1占有

shouldParkAfterFailedAcquire&&parkAndCheckInterrupt

设前驱为signal ,

parkAndCheckInterrupt阻塞当前线程(重量级) 此时线程的执行暂停了

3.selfInterrupt

static void selfInterrupt() {
    Thread.currentThread().interrupt();
}
中断当前线程

以上一节中3个线程竞争锁的代码为例子,

线程1在lock时,锁空闲,CAS可以成功,并将锁owner设为线程1

线程2在lock时,锁已经被线程1占有,acquire(1)抢占锁,

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

2.3.3 release(int)

ReentrantLock unlock委托给AQS的release(1)

public void unlock() {//ReentrantLock
     sync.release(1);
}
    public final boolean release(int arg) {//AQS
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
方法解析

1.线程1 tryRelease

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;
}

将exclusiveOwnerThread置空,并修改state

(由于只有一个线程能进入该代码,无需并发控制)

2.线程1 unparkSuccessor

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

找到第一个未中断取消的中继节点thread2,将其唤醒uppark

3.线程2继续执行acquireQueued的for循环

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; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed) cancelAcquire(node);
    }
}

此时线程2 tryAcquire可以获得锁,竞争锁成功return

setHead(node);设为头节点

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值