Java并发 - ReentrantLock锁

发布时间:2024年01月17日

带着问题阅读
1.什么是可重入锁?可重入锁解决什么问题?
2.ReentrantLock的核心是AQS,它是怎么实现的?
3.ReentrantLock中的公平锁和非公平锁?

ReentrantLock是Java中实现可重入锁的一个重要类,下面是对其源码的简要解析。请注意,这里只是对关键部分进行了概要说明,具体实现细节可能会有更多复杂性。

1. ReentrantLock源码解析

extends
extends
extends
Sync
NonfairSync
FairSync
AbstractQueuedSynchronizer

公平锁和非公平锁

ReentrantLock可以是公平锁或非公平锁。在公平锁模式下,线程按照申请锁的顺序来获取锁,不会产生饥饿现象。而非公平锁则允许线程在任意时刻争夺锁,可能导致某些线程长时间无法获取锁,存在饥饿现象。

1.1 Sync

这个组件是基于AQS(AbstractQueuedSynchronizer)的抽象类,用于实现ReentrantLock的同步机制。

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

    // lock方法是ReentrantLock类中用于获取锁的抽象方法
    abstract void lock();

    // 非公平锁的尝试获取锁的实现
    final boolean nonfairTryAcquire(int acquires) {
        // 获取当前线程
        final Thread current = Thread.currentThread();
        // 获取当前状态
        int c = getState();
        if (c == 0) { //为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;
    }
	// 在共享模式下获取对象状态
    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;
    }

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

    // 新生一个条件
    final ConditionObject newCondition() {
        return new ConditionObject();
    }

    // 返回占有资源的线程
    final Thread getOwner() {
        return getState() == 0 ? null : getExclusiveOwnerThread();
    }
    // 返回状态
    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); // reset to unlocked state
    }
}
方法作用
locklock方法是ReentrantLock类中用于获取锁的抽象方法。具体的实现在ReentrantLock的非公平锁和公平锁中有所不同
nonfairTryAcquirenonfairTryAcquire方法是非公平锁的尝试获取锁的实现。它首先检查当前同步状态是否为0,如果是,则通过CAS操作将其设置为acquires,表示获取锁成功。
tryReleasetryRelease方法是释放锁的实现。它首先检查当前线程是否为锁的持有者,如果不是则抛出IllegalMonitorStateException异常。
isHeldExclusively用于检查当前线程是否是锁的持有者。这里直接比较当前线程和独占锁的持有者线程,如果相等,则当前线程是持有者。
newCondition用于创建一个与ReentrantLock关联的Condition对象,用于支持更灵活的线程等待和唤醒操作
getOwner返回当前持有锁的线程,如果锁未被持有则返回null
getHoldCount返回当前线程持有锁的次数,如果不是持有者则返回0
isLocked用于检查锁是否被持有,即同步状态是否不为0
readObject用于反序列化,将实例的状态重置为未锁定状态

Sync是基于AbstractQueuedSynchronizer的抽象类,AQS是ReentrantLock的核心组件,它提供了一种便于实现各种同步器的框架。它基于FIFO队列实现了一套多线程同步的抽象框架,通过状态的获取和释放来实现线程的同步。

实现原理:

AQS使用一个int型的状态来表示资源的占用情况,通过CAS操作来保证状态的原子性。当线程尝试获取锁时,如果锁已被占用,线程会被加入到等待队列中。释放锁时,线程将释放锁的状态,并唤醒等待队列中的线程。

1.2 NonfairSync

NonfairSyncReentrantLock中非公平锁的实现,它继承了Sync类。

// 非公平锁
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;
    // 用于获取锁
    final void lock() {
        // 通过CAS操作将同步状态从0设置为1
        if (compareAndSetState(0, 1))
            // 设置成功,设置当前线程为独占锁的持有者
            setExclusiveOwnerThread(Thread.currentThread());
        else
            // 进入等待队列等待获取锁
            acquire(1);
    }

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

1.3 FairSync

FairSyncReentrantLock中公平锁的实现,它继承了Sync类。

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;
    // 直接调用了acquire方法,表示获取锁时要考虑等待队列中的其他线程,按照FIFO顺序获取锁
    final void lock() {
        acquire(1);
    }
    // 
    protected final boolean tryAcquire(int acquires) {
        // 获取当前线程
        final Thread current = Thread.currentThread();
        // 获取当前资源状态
        int c = getState();
        if (c == 0) {
            // hasQueuedPredecessors 检查当前线程前面是否有等待线程
            // compareAndSetState 比较和设置当前
            // setExclusiveOwnerThread 记录当前线程是锁的持有者
            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;
    }
}

可重入锁是一种支持线程重复获取同一把锁的锁机制。当一个线程已经获得某个锁时,它可以再次获取该锁,而不会被阻塞。这种锁机制允许同一线程在持有锁的情况下多次进入同步块或方法,而不会被自己持有的锁所阻塞。

可重入锁解决了两个主要问题:

  1. 避免死锁: 在多线程环境中,如果一个线程已经获得了锁A,而在持有锁A的情况下又试图获取锁B,而另一个线程已经获得了锁B,又试图获取锁A,就会发生死锁。可重入锁允许同一线程在持有锁的情况下继续获取其他锁,避免了死锁的发生。
  2. 支持递归调用: 在某些情况下,同一线程需要多次进入同步块或方法,而不希望被自己持有的锁所阻塞。可重入锁允许同一线程多次获取同一把锁,使得递归调用的情况更容易管理。

2. ReentrantLock的使用示例

1.1 公平锁示例

public class FairLockExample {
    private static final Lock fairLock = new ReentrantLock(true); // 公平锁

    public static void main(String[] args) {
        Runnable fairTask = () -> {
            try {
                fairLock.lock();
                System.out.println(Thread.currentThread().getName() + " acquired the lock");
                // 模拟对共享资源的操作
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + " releasing the lock");
                fairLock.unlock();
            }
        };

        // 创建多个线程并启动
        for (int i = 1; i <= 5; i++) {
            new Thread(fairTask, "Thread-" + i).start();
        }
    }
}
Connected to the target VM, address: '127.0.0.1:62892', transport: 'socket'
Thread-1 acquired the lock
Thread-1 releasing the lock
Thread-2 acquired the lock
Thread-2 releasing the lock
Thread-3 acquired the lock
Thread-3 releasing the lock
Thread-4 acquired the lock
Thread-4 releasing the lock
Thread-5 acquired the lock
Thread-5 releasing the lock
Disconnected from the target VM, address: '127.0.0.1:62892', transport: 'socket'

1.2 非公平锁示例

public class NonfairLockExample {
    private static final Lock nonfairLock = new ReentrantLock(); // 非公平锁

    public static void main(String[] args) {
        Runnable nonfairTask = () -> {
            try {
                nonfairLock.lock();
                System.out.println(Thread.currentThread().getName() + " acquired the lock");
                // 模拟对共享资源的操作
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + " releasing the lock");
                nonfairLock.unlock();
            }
        };

        // 创建多个线程并启动
        for (int i = 1; i <= 5; i++) {
            new Thread(nonfairTask, "Thread-" + i).start();
        }
    }
}
Connected to the target VM, address: '127.0.0.1:50980', transport: 'socket'
Thread-1 acquired the lock
Thread-1 releasing the lock
Thread-3 acquired the lock
Thread-3 releasing the lock
Thread-2 acquired the lock
Thread-2 releasing the lock
Thread-4 acquired the lock
Thread-4 releasing the lock
Thread-5 acquired the lock
Thread-5 releasing the lock
Disconnected from the target VM, address: '127.0.0.1:50980', transport: 'socket'

3. ReentrantLock的应用场景

  1. 替代synchronized关键字: ReentrantLock 提供了比 synchronized 更灵活的锁控制,允许更复杂的线程同步结构。在需要更多控制权和灵活性的场景下,可以使用 ReentrantLock 替代 synchronized
  2. 可中断的锁请求: ReentrantLock 提供了可中断的锁请求功能,即线程可以响应中断信号而不是无限等待锁。这对于避免死锁等问题很有帮助。
  3. 超时的锁请求: ReentrantLock 允许在尝试获取锁时设置超时时间,避免线程无限期地等待锁。
  4. 公平性控制: ReentrantLock 提供了可选的公平锁和非公平锁。在公平锁模式下,等待时间较长的线程更有可能获取锁,有助于避免饥饿情况。
  5. Condition条件变量的使用: ReentrantLock 支持通过 newCondition 方法创建与锁关联的 Condition 对象,从而实现更灵活的线程等待和通知机制。
  6. 可重入性控制: ReentrantLock 支持线程的可重入性,同一线程可以多次获取同一把锁,而不会造成死锁。
  7. 精确的锁释放控制: ReentrantLock 提供了对锁释放的精确控制,可以在特定条件下选择是否释放锁,这对于实现一些特殊的同步逻辑很有帮助。
  8. 性能优化: 在某些情况下,使用 ReentrantLock 可以提供更好的性能,特别是在高并发环境下。

总的来说,ReentrantLock 适用于需要更灵活、更可控的线程同步机制的场景。在这些场景中,它提供了比传统的synchronized关键字更多的功能和选项。

文章来源:https://blog.csdn.net/qq_43678225/article/details/135642870
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。