ReentrantLock和AQS源码解析

15 阅读17分钟

本文源码基于JDK 1.8

在 Java 中,Dong Lea 大师为我们提供了大量并发编程工具类,它们都在 JDK 的 java.util.concurrent 包下,其目录结构如下:

image.png

current 包中包含很多我们常用的并发工具类,如:ConcurrentHashMap、CopyOnWriteArrayList、ArrayBlockingQueue、LinkedBlockingQueue、ThreadPoolExecutor 等。

其中还包含了 2 个子包: atomic 和 locks 。atomic 包下有 AtomicBoolean、AtomicInteger、atomicReference 等 CAS 原子变量类, locks 包下有 ReentrantLock、ReentrantReadWriteLock、AbstractQueuedSynchronizer 等锁及锁的底层实现基础框架。这些类的实现主要是依赖于 volatile 及 CAS ,从整体上来看 concurrent 包的整体实现图如下图所示:

image.png

Lock

锁能够解决多个线程同时访问共享资源造成的同步问题,在 Lock 接口出现之前, Java 主要是靠 synchronized 关键字来实现锁功能, Java SE5 之后并发包中增加了 Lock 接口,它提供了与 synchronized 一样的锁功能。它虽然没有使用 synchronize 关键字那么便捷,但是它拥有获取和释放锁的可操作性,还有可中断地获取锁以及超时获取锁等多种 synchronized 关键字所不具备的同步特性。

Lock 是一个接口,里面有 6 个方法:

public interface Lock {

    // 获取锁,无返回值,如果没有拿到锁将无法进行线程调度并处于休眠状态直到拿到锁
    void lock();

    // 和上面不同的地方是获取锁的过程能够响应中断
    void lockInterruptibly() throws InterruptedException;

    // 获取锁,有返回值,能拿到锁立即返回 true ;否则返回 false ;
    boolean tryLock();

    // 在超时时间内并且没有被中断的情况下如果锁是空闲的就能拿到锁
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    // 释放锁
    void unlock();

    // 返回跟锁实例绑定的Condition实例
    Condition newCondition();
}

ReentrantLock

ReentrantLock 实现了 Lock 接口,代码如下:

public class ReentrantLock implements Lock, java.io.Serializable {

    private final Sync sync;

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

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

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

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

}

从上面的代码可以看到里面的方法都是通过 sync 来实现的,如果调用 ReentrantLock 无参的构造方法,sync 就是 NonfairSync 的实例;如果使用 new ReentrantLock(true) ,则 sync 就是 FairSync 的实例。

他们俩分别是非公平锁和公平锁,由此可知 ReentrantLock 默认是非公平锁, NonfairSync 和 FairSync 的代码如下:

public class ReentrantLock implements Lock, java.io.Serializable {

    /**
     * Sync object for non-fair locks
     */
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock. Try immediate barge, backing up to normal
         * acquire on failure.
         */
        // Android-removed: @ReservedStackAccess from OpenJDK 9, not available on Android.
        // @ReservedStackAccess
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            // 调用 nonfairTryAcquire() 方法
            return nonfairTryAcquire(acquires);
        }
    }

    /**
     * Sync object for fair locks
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

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

        /**
         * Fair version of tryAcquire. Don't grant access unless
         * recursive call or no waiters or is first.
         */
        // Android-removed: @ReservedStackAccess from OpenJDK 9, not available on Android.
        // @ReservedStackAccess
        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()) {
                // 再次获取,计数加 1
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }


    /**
     * Base of synchronization control for this lock. Subclassed
     * into fair and nonfair versions below. Uses AQS state to
     * represent the number of holds on the lock.
     */
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;

        /**
         * Performs {@link Lock#lock}. The main reason for subclassing
         * is to allow fast path for nonfair version.
         */
        abstract void lock();

        /**
         * Performs non-fair tryLock. tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
        // Android-removed: @ReservedStackAccess from OpenJDK 9, not available on Android.
        // @ReservedStackAccess
        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()) {
                // 再次获取,计数加1
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

        // Android-removed: @ReservedStackAccess from OpenJDK 9, not available on Android.
        // @ReservedStackAccess
        protected final boolean tryRelease(int releases) {
            //计数减1
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //计数为0时,当前线程释放锁
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            //计数不为0时,返回false
            return free;
        }

        protected final boolean isHeldExclusively() {
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

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

        // Methods relayed from outer class

        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }

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

        /**
         * Reconstitutes the instance from a stream (that is, deserializes it).
         */
        private void readObject(java.io.ObjectInputStream s)
                throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }
}

可以看到 NonfairSync 和 FairSync 均继承自 Sync 类,Sync 类是 ReentrantLock 类中的抽象静态内部类, Sync 类继承自 AbstractQueuedSynchronizer 类,Sync 类中的 lock() 方法是一个抽象方法,子类必须重写。 NonfairSync 和 FairSync 类中分别对 lock() 方法进行了不同的重写实现。其区别是 NonfairSync 中的 lock() 方法会先使用 CAS 尝试获取锁,如果成功了,设置当前线程为拥有独占访问权限的线程,如果 CAS 不成功,再调用 AQS 中的 acquire(1) ,而 FairSync 中的 lock() 方法直接调用了 acquire(1) , acquire() 方法来自 AbstractQueuedSynchronizer ,代码如下:

public abstract class AbstractQueuedSynchronizer {

    public final void acquire(int arg) {
        // 先使用 tryAcquire(arg) 获取锁,公平锁和非公平锁分别对
        // tryAcquire() 进行了不同的重写,addWaiter() 会将当前线程
        // 对应的 Node 加入等待双向链表,acquireQueued()可以对排队
        // 中的线程进行获取锁的操作
        if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
}

其中 tryAcquire() 方法在 NonfairSync 和 FairSync 类中也有不同的重写实现,主要区别是 FairSync 中会先判断等待队列中是否有前驱节点,如果有前驱节点说明有线程比当前线程更早获取锁,根据公平性,当前线程获取锁失败。

以上就是 ReentrantLock 中公平锁与非公平锁的区别。

直接从名字就可以看出来, ReentrantLock 是可重入锁。从代码中也可以看到,tryAcquire() 和 nonfairTryAcquire() 方法中判断如果当前线程已经持有了锁,计数加 1 。在 tryRelease() 中计数减1,直到计数为 0 才释放锁,所以使用 ReentrantLock 重复对一个线程加锁并不会导致死锁。

AbstractQueuedSynchronizer

Sync 类继承自 AbstractQueuedSynchronizer 类,AbstractQueuedSynchronizer 是什么呢?源码中有十分具体的解释:

Provides a framework for implementing blocking locks and related synchronizers (semaphores, events, etc) that
rely on first-in-first-out (FIFO) wait queues. This class is designed to be a useful basis for most kinds of
synchronizers that rely on a single atomic int value to represent state. Subclasses must define the protected
methods that change this state, and which define what that state means in terms of this object being acquired
or released. Given these, the other methods in this class carry out all queuing and blocking mechanics.
Subclasses can maintain other state fields, but only the atomically updated int value manipulated using
methods getState, setState and compareAndSetState is tracked with respect to synchronization

Subclasses should be defined as non-public internal helper classes that are used to implement the
synchronization properties of their enclosing class. Class AbstractQueuedSynchronizer does not implement
any synchronization interface. Instead it defines methods such as acquireInterruptibly that can be invoked
as appropriate by concrete locks and related synchronizers to implement their public methods.

This class supports either or both a default exclusive mode and a shared mode. When acquired in exclusive
mode, attempted acquires by other threads cannot succeed. Shared mode acquires by multiple threads may
(but need not) succeed. This class does not "understand" these differences except in the mechanical sense
that when a shared mode acquire succeeds, the next waiting thread (if one exists) must also determine whether
it can acquire as well. Threads waiting in the different modes share the same FIFO queue. Usually,
implementation subclasses support only one of these modes, but both can come into play for example in a
ReadWriteLock. Subclasses that support only exclusive or only shared modes need not define the methods supporting the unused mode.

AbstractQueuedSynchronizer 是用来实现锁和相关同步器(信号量、事件等)的基础框架,它的实现主要依赖一个 atomic int 成员变量表示的同步状态和一个 FIFO 队列构成的等待队列。子类必须重写 AQS 的 protected 修饰的用来改变同步状态的方法, AQS 的其他几个方法实现了排队和阻塞机制。子类可以维护其他状态字段,但是建议更新同步状态使用 getState() 、 setState() 以及 compareAndSetState() 这三个方法。

子类应定义为非公共内部帮助类用于实现其封闭类的同步属性,AQS 自身没有实现任何同步接口,它定义了 acquireInterruptibly() 这类方法,它可以被具体的锁和相关同步器里面的公共方法调用。

该类支持默认的独占式获取同步状态和共享式获取同步状态的两者或者之一,这样就可以方便的实现不同类型的同步组件。

什么是独占锁?什么是共享锁?
独占锁也叫排它锁,是指该锁一次只能被一个线程持有,如果别的线程想要获取锁,只有等到持有锁的线程释放锁。获得排它锁的线程既能读取数据又能修改数据,与之对立的就是共享锁。共享锁是指该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁。获得共享锁的线程只能读数据,不能修改数据

AQS 使用了模板方法设计模式,子类必须重写 AQS 的 protected 修饰的用来改变同步状态的方法,还要在自己的 public 方法中调用 acquireInterruptibly() 这类模板方法。例如 AQS 中的tryAcquire(int arg)方法就是一个被 protected 修饰的用来改变同步状态的方法,在前面的代码中可以看到 ReentrantLock 的 FairSync 和 NonfairSync 对 tryAcquire(int arg) 方法进行了对不同的重写,而 acquireInterruptibly(int arg) 则被 ReentrantLock 中的 lockInterruptibly() 的 public 方法调用:

public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}

其中 AQS 可以重写的方法如下:

/**
 * Attempts to acquire in exclusive mode. This method should query
 * if the state of the object permits it to be acquired in the
 * exclusive mode, and if so to acquire it.
 * 独占式获取同步状态。该方法应该查询是否允许独占式获取同步状态,如果可以就使用CAS获取同步状态
 */
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

/**
 * Attempts to set the state to reflect a release in exclusive
 * mode.
 *
 * <p>This method is always invoked by the thread performing release.
 * 独占式释放锁,等待获取同步状态的线程将有机会获取同步状态
 */
protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

/**
 * Attempts to acquire in shared mode. This method should query if
 * the state of the object permits it to be acquired in the shared
 * mode, and if so to acquire it.
 *
 * <p>This method is always invoked by the thread performing
 * acquire. If this method reports failure, the acquire method
 * may queue the thread, if it is not already queued, until it is
 * signalled by a release from some other thread.
 *
 * <p>The default implementation throws {@link
 * UnsupportedOperationException}.
 * 共享式获取同步状态,返回值大于等于0表示获取成功,反之获取失败
 */
protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}

/**
 * Attempts to set the state to reflect a release in shared mode.
 *
 * <p>This method is always invoked by the thread performing release.
 *
 * <p>The default implementation throws
 * {@link UnsupportedOperationException}.
 * 共享式释放同步状态
 */
protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}

/**
 * Returns {@code true} if synchronization is held exclusively with
 * respect to the current (calling) thread. This method is invoked
 * upon each call to a non-waiting {@link ConditionObject} method.
 * (Waiting methods instead invoke {@link #release}.)
 * 同步器是否被当前线程独占
 */
protected boolean isHeldExclusively() {
    throw new UnsupportedOperationException();
}

AQS 提供的模板方法如下:

/**
 * Acquires in exclusive mode, ignoring interrupts. Implemented
 * by invoking at least once {@link #tryAcquire},
 * returning on success. Otherwise the thread is queued, possibly
 * repeatedly blocking and unblocking, invoking {@link
 * #tryAcquire} until success. This method can be used
 * to implement method {@link Lock#lock}.
 * 独占式获取同步状态,如果成功直接结束,如果失败线程将会进入同步队列中等待,该方法会调用重写的
 * tryAcquire()方法
 */
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

/**
 * Acquires in exclusive mode, aborting if interrupted.
 * Implemented by first checking interrupt status, then invoking
 * at least once {@link #tryAcquire}, returning on
 * success. Otherwise the thread is queued, possibly repeatedly
 * blocking and unblocking, invoking {@link #tryAcquire}
 * until success or the thread is interrupted. This method can be
 * used to implement method {@link Lock#lockInterruptibly}.
 * 与acquire(int arg)相同,但该方法可以响应中断,被中断时抛出InterruptedException
 */
public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

/**
 * Attempts to acquire in exclusive mode, aborting if interrupted,
 * and failing if the given timeout elapses. Implemented by first
 * checking interrupt status, then invoking at least once {@link
 * #tryAcquire}, returning on success. Otherwise, the thread is
 * queued, possibly repeatedly blocking and unblocking, invoking
 * {@link #tryAcquire} until success or the thread is interrupted
 * or the timeout elapses. This method can be used to implement
 * method {@link Lock#tryLock(long, TimeUnit)}.
 * 在InterruptedException(int arg)基础上增加了超时限制,如果当前线程在超时时间内
 * 没有获取到同步状态,将会返回false,获取到了返回true
 */
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
}


/**
 * Acquires in shared mode, ignoring interrupts. Implemented by
 * first invoking at least once {@link #tryAcquireShared},
 * returning on success. Otherwise the thread is queued, possibly
 * repeatedly blocking and unblocking, invoking {@link
 * #tryAcquireShared} until success.
 * 共享式的获取同步状态,如果当前线程没有获取到同步状态,将会进入同步队列中
 * 等待,与独占式获取的主要区别是在同一时刻可以有多个线程获取同步状态
 */
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

/**
 * Acquires in shared mode, aborting if interrupted. Implemented
 * by first checking interrupt status, then invoking at least once
 * {@link #tryAcquireShared}, returning on success. Otherwise the
 * thread is queued, possibly repeatedly blocking and unblocking,
 * invoking {@link #tryAcquireShared} until success or the thread
 * is interrupted.
 * 与acquireShared(int arg)相同,该方法响应中断
 */
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

/**
 * Attempts to acquire in shared mode, aborting if interrupted, and
 * failing if the given timeout elapses. Implemented by first
 * checking interrupt status, then invoking at least once {@link
 * #tryAcquireShared}, returning on success. Otherwise, the
 * thread is queued, possibly repeatedly blocking and unblocking,
 * invoking {@link #tryAcquireShared} until success or the thread
 * is interrupted or the timeout elapses.
 * 在acquireSharedInterruptibly(int arg)基础上增加了超时限制
 */
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    return tryAcquireShared(arg) >= 0 ||
            doAcquireSharedNanos(arg, nanosTimeout);
}

/**
 * Releases in exclusive mode. Implemented by unblocking one or
 * more threads if {@link #tryRelease} returns true.
 * This method can be used to implement method {@link Lock#unlock}.
 * 独占式的释放同步状态,该方法会在释放同步状态之后,将队头节点
 * 的线程唤醒
 */
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

/**
 * Releases in shared mode. Implemented by unblocking one or more
 * threads if {@link #tryReleaseShared} returns true.
 * 共享式的释放同步状态
 */
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

/**
 * Returns the first (longest-waiting) thread in the queue, or
 * {@code null} if no threads are currently queued.
 *
 * <p>In this implementation, this operation normally returns in
 * constant time, but may iterate upon contention if other threads are
 * concurrently modifying the queue.
 * 获取等待在同步队列上的线程集合
 */
public final Thread getFirstQueuedThread() {
    // handle only fast path, else relay
    return (head == tail) ? null : fullGetFirstQueuedThread();
}

AQS 提供的模板方法可以分为3类:

  1. 独占式获取与释放同步状态;
  2. 共享式获取与释放同步状态;
  3. 查询同步队列中等待线程情况;

源码中有一个例子可以帮助我们理解如何使用 AQS 实现自己的同步锁,这个例子是一个不可重入互斥锁类,用 0 表示解锁状态,1 表示表示锁定状态:

class Mutex implements Lock, java.io.Serializable {

    // Our internal helper class
    private static class Sync extends AbstractQueuedSynchronizer {
        // Reports whether in locked state
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        // Acquires the lock if state is zero
        public boolean tryAcquire(int acquires) {
            assert acquires == 1; // Otherwise unused
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        // Releases the lock by setting state to zero
        protected boolean tryRelease(int releases) {
            assert releases == 1; // Otherwise unused
            if (getState() == 0) throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        // Provides a Condition
        Condition newCondition() {
            return new ConditionObject();
        }

        // Deserializes properly
        private void readObject(ObjectInputStream s)
                throws IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }

    // The sync object does all the hard work. We just forward to it.
    private final Sync sync = new Sync();

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

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

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

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

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

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

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

}
public class MutexDemo {
    private static Mutex mutex = new Mutex();

    static int count = 0;

    private static void increase() {
        for (int j = 0; j < 10000; j++) {
            count++; //count++不具备原子性
        }
    }

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            mutex.lock();
            try {
                increase();
            } finally {
                mutex.unlock();
            }

        });
        thread1.start();


        Thread thread2 = new Thread(() -> {
            mutex.lock();
            try {
                increase();
            } finally {
                mutex.unlock();
            }

        });
        thread2.start();

        try {
            thread1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        try {
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("the count is: " + count);
    }

}

打印结果为:

the count is: 20000

由于 count++ 不具备原子性,它是先把 count 加 1 然后再写入新的 count 值。如果不添加锁,打印出来的值很大可能小于20000。

从这个例子可以看到,使用AQS可以很方便地实现同步组件。

ReentrantLock 的优势

ReentrantLock 提供了比 synchronized 关键字更灵活的锁定机制,可以在以下情况下优先于 synchronized 使用:

  1. 需要显式的锁定和解锁ReentrantLock 允许你显式地获取和释放锁,提供了比 synchronized 更细粒度的控制。你可以在不同的方法或代码块中获取和释放同一个锁,而不像 synchronized 必须在同一个代码块中释放
  2. 需要尝试锁定: 使用 ReentrantLock 时可以调用 tryLock() 方法,尝试获取锁而不会一直阻塞。如果锁被其他线程持有,tryLock() 可以返回 false,允许你根据情况采取其他操作。
  3. 需要可中断的锁ReentrantLock 提供了 lockInterruptibly() 方法,使得线程可以在等待获取锁时响应中断请求。synchronized 则无法响应中断,线程只能一直阻塞,直到获取锁。
  4. 需要公平锁ReentrantLock 提供了公平锁的机制(通过构造方法 new ReentrantLock(true)),这意味着锁将按照线程请求的顺序进行分配,而 synchronized 是非公平的,线程获取锁的顺序可能无法预测。
  5. 需要多条件等待ReentrantLock 支持多个 Condition 对象,允许线程基于不同的条件来等待和通知,增强了灵活性。相比之下,synchronized 只能使用单个 wait()notify() 机制来进行线程间的通信。
  6. 性能需求: 在某些高并发场景下,ReentrantLock 的性能可能会优于 synchronized。它的实现更加优化,减少了锁争用的开销。

总的来说,ReentrantLock 适用于需要更灵活锁定机制、更多控制权或者更复杂并发控制场景的情况。对于简单的同步需求,synchronized 关键字依然是首选,因为它更简洁,且在绝大多数场景中性能表现良好。

假设我们有一个任务系统,其中多个线程尝试执行一项关键任务。我们希望:

  1. 线程在等待获取锁时有一个超时时间,如果超过这个时间未能获取锁,则执行其他操作或退出。
  2. 线程在等待锁时可以被中断,而不必一直等待。

在这种情况下,synchronized 无法提供这样的灵活性,但 ReentrantLock 可以通过 tryLock()lockInterruptibly() 方法来实现。

看如下代码示例:使用 ReentrantLock 实现超时和响应中断

class CriticalTask {
    private final ReentrantLock lock = new ReentrantLock();

    // 任务方法,尝试在超时内获取锁
    public void performTask(String threadName) {
        try {
            // 尝试在 2 秒内获取锁,超时则返回 false
            if (lock.tryLock(2, TimeUnit.SECONDS)) {
                try {
                    System.out.println(threadName + " 成功获取锁,正在执行任务...");
                    // 模拟任务执行
                    Thread.sleep(1000);
                    System.out.println(threadName + " 任务完成");
                } finally {
                    lock.unlock(); // 释放锁
                    System.out.println(threadName + " 已释放锁");
                }
            } else {
                System.out.println(threadName + " 获取锁超时,无法执行任务");
            }
        } catch (InterruptedException e) {
            System.out.println(threadName + " 被中断,任务中止");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        CriticalTask task = new CriticalTask();

        // 创建两个线程,模拟并发任务执行
        Thread t1 = new Thread(() -> task.performTask("线程1"));
        Thread t2 = new Thread(() -> task.performTask("线程2"));

        t1.start();
        t2.start();

        // 中断线程t1以模拟中断情况
        try {
            Thread.sleep(500);
            t1.interrupt(); // 中断t1
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

这里 tryLock(2, TimeUnit.SECONDS) 线程尝试获取锁,但如果在 2 秒内未能获取锁,则会返回 false,从而避免线程永久等待锁的情况。synchronized 无法实现这种超时机制。并且我们在代码中模拟了线程 t1 被中断的情况。如果一个线程在等待锁时被中断,ReentrantLock 的 lockInterruptibly() 方法可以捕获 InterruptedException,从而中止操作并及时响应。synchronized 无法响应中断,线程只能一直阻塞直到锁被释放。