源码分析--ReentrantLock

16 篇文章 0 订阅

常用方法

ReentrantLock 常用的方法就是 Lock 接口定义的几个方法,如下:

// 获取锁(阻塞式)
public void lock() {
    sync.lock();
}

// 获取锁(响应中断)
public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}

// 尝试获取锁
public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
}

// 尝试获取锁(有超时等待)
public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

// 释放锁
public void unlock() {
    sync.release(1);
}

这几个方法内部都是通过调用 Sync 类(或其子类)的方法来实现。具体如下:

// 抽象类,继承了 AQS
abstract static class Sync extends AbstractQueuedSynchronizer {

    // 获取锁的方法,由子类实现
    abstract void lock();

    // 非公平锁的 tryLock 方法实现
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        // 获取 AQS 的 state 变量
        int c = getState();
        // 若为 0,表示当前没有被其他线程占用
        if (c == 0) {
            // CAS 修改 state,若修改成功,表示成功获取资源
            if (compareAndSetState(0, acquires)) {
                // 将当前线程设置为 owner,到这里表示当前线程成功获取资源
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        // state 不为 0,且 owner 为当前线程
        // 表示当前线程已经获取到了资源,这里表示“重入”
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            // 修改 state 值(因为当前线程已经获取资源,不存在竞争,因此无需 CAS 操作)
            setState(nextc);
            return true;
        }
        return false;
    }

    // 释放锁操作(对 state 做减法)
    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;
            // 成功释放后将 owner 设为空
            setExclusiveOwnerThread(null);
        }
        // 修改 state 的值
        // PS: 因为可能存在“重入”,因此一次释放操作后当前线程仍有可能占用资源,
        // 所以不会直接把 state 设为 0
        setState(c);
        return free;
    }

    // 其他方法...

    final boolean isLocked() {
        return getState() != 0;
    }
}

锁的获取和释放是通过修改 AQS 的 state 变量来实现的。lock 方法可以看做对 state 执行“加法”操作,而 unlock 可以看做对 state 执行“减法”操作,当 state 为 0 时,表示当前没有线程占用资源。

公平锁&非公平锁

公平锁:

static final class FairSync extends Sync {

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

    // 公平锁的 tryAcquire 实现
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        // state 为 0,表示资源未被占用
        if (c == 0) {
            // 若队列中有其他线程在排队等待,则返回 false,表示获取失败;
            //   否则,再尝试去修改 state 的值
            // PS: 这里是公平锁与非公平锁的区别所在
            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;
    }
}

非公平锁:

static final class NonfairSync extends Sync {

    final void lock() {
        // CAS 尝试将 state 值修改为 1
        if (compareAndSetState(0, 1))
            // 若修改成功,则将当前线程设为 owner,表示成功获取锁
            setExclusiveOwnerThread(Thread.currentThread());
        // 若获取失败,则执行 AQS 的 acquire 方法(独占模式获取资源)
        else
            acquire(1);
    }

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

区别:
非公平锁再获取锁的时候不按排队顺序而是随机拿锁 
tryAcquire方法中!hasQueuedPredecessors() 来判定队列中是否有前驱节点在等待锁

Java中的ReentrantLock是一个可重入锁的实现。它允许一个线程多次获取同一个锁,在获得锁的时候锁的计数器会加1,每次释放锁的时候计数器会减1,只有当锁的计数器变为0时,其他线程才能获取到该锁。

ReentrantLock的主要实现是通过继承AQS(AbstractQueuedSynchronizer)类来实现的。AQS类提供了同步器的框架,是Java并发包中重要的基础设施。AQS类中有两个主要的变量同步队列和状态量,线程通过AQS的acquire和release方法实现对同步队列和状态量的操作。

当一个线程获取锁时,如果锁没有被其他线程占用,那么该线程直接将状态量设为1,表示占用了锁。如果锁已经被占用了,那么当前线程就会被加入到同步队列中,等待其他释放锁的线程通知它。当一个线程释放锁时,如果同步队列中有等待线程,则会唤醒其中的一个线程,令其获取到锁。如果同步队列中有多个等待线程,则会唤醒其中最先入队列的线程。

在多线程环境下,可重入锁ReentrantLock的实现如下:

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    private final Sync sync;

    abstract static class Sync extends AbstractQueuedSynchronizer {
        abstract void lock();

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

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

        final ConditionObject newCondition() {
            return new ConditionObject();
        }
    }

    final static class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

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

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

    final static 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) {
                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;
        }
    }

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

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

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

    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

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

    public Condition newCondition() {
        return sync.newCondition();
    }

    public int getHoldCount() {
        return sync.getHoldCount();
    }

    public boolean isLocked() {
        return sync.isLocked();
    }

    public boolean isFair() {
        return sync instanceof FairSync;
    }

    protected Thread getOwner() {
        return sync.getOwner();
    }
}

其中Sync是一个抽象类,它继承了AQS类,并提供了可重入锁的主要实现。NonfairSync和FairSync分别表示不公平的锁和公平的锁,以控制线程抢占锁的顺序。

在lock方法中,当当前状态量为0时,线程会通过compareAndSetState方法将状态量设置为1,同时获取独占线程的所有权,表示 lock被当前线程占用,如果当前状态量不为0且当前线程是之前占用锁的线程,将状态量加1,表示占用的锁的数量加1。

在unlock方法中,如果线程并没有占用锁直接释放,那么就会抛出异常,如果线程是占用锁的线程,那么就将锁的数量减1。如果当前锁的数量为0,表明线程已经完全释放了该锁,那么独占线程将被置为null,同时同步队列中的锁将被唤醒。

ReentrantLock的主要思想就是通过利用同步队列和状态量来实现线程之间的协作,让线程在共享资源时能够互相协作,让占用资源的线程都按顺序地释放资源,避免竞争条件的发生。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值