Java 多线程学习之 ReentrantLock

一、什么是 ReentrantLock

ReentrantLock 中文译为‘可重入锁’,是 java.util.concurrent.locks 包下的一个类,实现了 Lock 接口,在多线程中用来保证线程安全。

使用 demo:

public static void main(String[] args) {
    ReentrantLock lock = new ReentrantLock();
    new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                int i = 1;
                lock.lock();
                System.out.println("第" + i + "次获取锁,这个锁是:" + lock);
                for (i = 2; i < 10; i++) {
                    try {
                        lock.lock();
                        System.out.println("第" + i + "次获取锁,这个锁是:" + lock);
                    } finally {
                        lock.unlock();
                    }
                }
            } finally {
                lock.unlock();
            }
        }
    }).start();
}
  

二、公平锁和非公平锁

ReentrantLock 有三个内部类:SyncNonfairSyncFairSyncSync 继承了抽象类 AbstractQueuedSynchronizer(俗称 AQS),NonfairSyncFairSync 都继承了 Sync,分别采用了非公平锁和公平锁的策略去获取锁。

公平锁

公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。
优点:所有的线程都能得到资源,不会饿死在队列中。
缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu 唤醒阻塞线程的开销会很大。

非公平锁

非公平锁:多个线程去获取锁的时候,会直接先去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
优点:可以减少 CPU 唤醒线程的开销,整体的吞吐效率会高点,CPU 也不必取唤醒所有线程,会减少唤起线程的数量。
缺点:这样可能导致队列中间的某个线程一直获取不到锁或者长时间获取不到锁。

根据 ReentrantLock 的构造函数可知:ReentrantLock 默认采用非公平锁:

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

三、ReentrantLock 和 AQS

1. ReentrantLock 和 AQS 的关系

ReentrantLock 使用 demo:

public static void main(String[] args) {
    ReentrantLock reentrantLock = new ReentrantLock();
    try {
        reentrantLock.lock();
        System.out.println("lock");
    } finally {
        reentrantLock.unlock();
    }
}
  

点进 lock()unlock() 方法,发现实际上是内部类 Sync 在调用 lock()unlock() 方法,而 Sync 又是继承的 AbstractQueuedSynchronizerAQS),所以 ReentrantLock 执行的方法实际上是通过 AQS 来实现的!

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

2. SyncNonfairSyncFairSync 源码分析

Sync:

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;

    //上锁
    abstract void lock();

    //非公平获取
    final boolean nonfairTryAcquire(int acquires) {
        //获取当前线程
        final Thread current = Thread.currentThread();
        //获取 AQS 的 state 值
        int c = getState();
        if (c == 0) {
            //如果 state 为 0 说明资源未被占用即当前线程获取资源成功
            //用 CAS 更新 state
            if (compareAndSetState(0, acquires)) {
                //设置占用线程为当前线程
                setExclusiveOwnerThread(current);
                return true;
            }
            //如果获取失败则把线程改为占用资源的线程
        } else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) // 正常情况下 state 不可能小于 0
                throw new Error("Maximum lock count exceeded");
            //更新 state 值
            setState(nextc);
            return true;
        }
        return false;
    }

    //释放资源
    protected final boolean tryRelease(int releases) {
        //获取当前 state
        int c = getState() - releases;
        //释放资源的前提是占据了资源,否则不正常,报错!
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        //state 为 0 说明当前线程是最后一个获取资源的线程,释放后设置占用资源线程为空
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        //更新 state
        setState(c);
        return free;
    }

    //判断当前线程是否为占用资源线程
    protected final boolean isHeldExclusively() {
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

    //创建实例
    final ConditionObject newCondition() {
        return new ConditionObject();
    }

    //获取占用线程
    final Thread getOwner() {
        return getState() == 0 ? null : getExclusiveOwnerThread();
    }

    //获取 state 值
    final int getHoldCount() {
        return isHeldExclusively() ? getState() : 0;
    }

    //判断资源是否被占用(被锁)
    final boolean isLocked() {
        return getState() != 0;
    }

    //自定义反序列化逻辑
    private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        setState(0); // 重置未被锁的状态
    }
}
  

NonfairSync:

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;
    //上锁
    final void lock() {
        //先去尝试获取资源 (修改成功则表示获取成功,否则表示获取失败)
        if (compareAndSetState(0, 1))
            //设置当前线程为占用资源线程
            setExclusiveOwnerThread(Thread.currentThread());
        else
            //获取失败则加入到等待队列中
            acquire(1);
    }

    //尝试获取资源,实际是 Sync 中的“非公平获取”方法的结果
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}
  

FairSync:

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();
        //获取 state
        int c = getState();
        if (c == 0) {
            //等待队列中前面没有其他线程且能成功修改 state 值时才把自己设为占据资源线程
            if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
                //设为资源拥有者
                setExclusiveOwnerThread(current);
                return true;
            }
            //如果当前线程已占据资源的话,不舍弃资源,仅修改 state 值
        } else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        //如果当前线程没有占据资源,且当前 state 值不为 0,则返回 false
        return false;
    }
}
  

分析:

ReentrantLocklock :实际上是内部 NonfairSyncFairSync 调用的 lock() 方法,而两者的区别在于:NonfairSync(非公平锁)会先判断能否获取资源,获取不到再把线程加到等待队列中;FairSync(公平锁)会直接加到等待队列中,等轮到自己的时候才能获取到资源。

ReentrantLockunlock:实际是内部类 Syncrelease() 方法,而在 release() 中又调用了 tryRelease(arg) ,因为 tryRelease 方法是在内部类 Sync 中实现的,所以并不分“公平锁”和“非公平锁”,即 ReentrantLock 无论用公平锁还是非公平锁,它的解锁方法都是统一的。

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 多线程学习之 ReentrantLock