ReentrantLock(独占锁)

ReentrantLock 是Java中的一个工具类,位于 java.util.concurrent.locks 包下,是一种可重入的互斥锁,是 Lock 接口的一个实现。ReentrantLock 是由java提供的一种能够进行显示同步操作的锁,和synchronized不同的是,它是通过代码的方式来控制锁的获取和释放。

java 复制代码
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private final ReentrantLock lock = new ReentrantLock();

    public void accessResource() {
        lock.lock();  // 获取锁

        try {
            // 保护的代码块
            System.out.println(Thread.currentThread().getName() + " is accessing the resource");
        } finally {
            lock.unlock();  // 释放锁
        }
    }

    public static void main(String[] args) {
        final ReentrantLockExample example = new ReentrantLockExample();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                example.accessResource();
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                example.accessResource();
            }
        });

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

这个例子中,我们使用 ReentrantLock 来保护一个共享资源的访问,通过调用 lock 方法获取锁,然后在 try 代码块中访问共享资源,最后在 finally 代码块中释放锁,这样可以确保即使在访问共享资源时发生异常,也可以正确地释放锁。 main 方法中,我们创建了两个线程同时访问这个共享资源。由于我们对访问共享资源的方法使用了锁进行了保护,所以即使有多个线程同时访问,也可以保证每次只有一个线程能访问这个方法。

公平锁和非公平锁

ReentrantLock 在创建时可以通过构造函数传入 fair 参数来决定这把锁是公平锁还是非公平锁。如果不传,默认是非公平锁。

java 复制代码
/**
* 公平锁
*/
private static ReentrantLock lock = new ReentrantLock(true);

/**
* 非公平锁
*/
private static ReentrantLock lock = new ReentrantLock();

公平锁

是指多个线程按照申请锁的顺序来获取锁,避免"饥饿"现象。在ReentrantLock内部,通过一个双向链表来管理等待锁的线程,越早等待的线程越早获取锁。

加锁底层实现

java 复制代码
    // 加锁逻辑
    final boolean nonfairTryAcquire(int acquires) {
         // 获取当前线程
        final Thread current = Thread.currentThread();
        // 获取state状态
        int c = getState();
        if (c == 0) { // 没有线程持有锁
            // cas尝试获取锁
            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");
            // 更新state状态
            setState(nextc);
            return true;
        }
        // 加锁失败
        return false;
    }
  1. 创建非公平的lock,调用lock.lock()进行加锁
  2. 调用CAS尝试加锁(修改state值由0->1,如果上一个持有锁的线程刚好释放,有可能会直接获取到)
    • cas操作成功,state值为1,将当前线程赋值到exclusiveOwnerThread属性(独占线程表标识,做可重入锁的判断依据)
    • cas操作失败,进入acquire(1)方法
      • 调用tryAcquire(1)方法,首先判断state是否等于0,是则尝试加锁,否则判断当前线程是不是exclusiveOwnerThread标识的线程,是的话state值+1(可重入实现)
      加锁失败,调用acquireQueued入队,入队前还会判断是否已经有队列,没有先创建队列(通过CAS保证只有一个线程去创建),将当前拿到锁的线程放到队首,然后当前加锁失败的线程加入到队列中等待唤醒,阻塞前还会将前置节点的waitState置为-1,便于唤醒
java 复制代码
    // 入队逻辑
    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;
                }
                
                // 阻塞线程前修改waitStatus,然后调用park()阻塞线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

解锁底层实现

java 复制代码
    // 解锁逻辑
    protected final boolean tryRelease(int releases) {
        // state-1
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread()) {
            throw new IllegalMonitorStateException();
        }
        // 默认false,有可能是重入锁,c != 0 说明当前线程还有是重入锁,还没有完全释放
        boolean free = false;
        if (c == 0) { // 锁释放成功
            free = true;
            setExclusiveOwnerThread(null);
        }
        // 更新state状态
        setState(c);
        return free;
    }
  1. 调用lock.unlock()
  2. 调用release(1)方法
    • 调用tryRelease(1)方法,首先获取state执行-1操作,如果-1后的值为0,将exclusiveOwnerThread置空,最后将-1后的值赋值到state,释放锁
    • 锁释放成功,唤醒队列中下一个线程节点,当前线程出队

非公平锁

是指多个线程获取锁的顺序并不是按照申请锁的顺序进行的。这样可能造成优先级高的线程因为等待而导致一种称为"饥饿"的现象,但是相对于公平锁,非公平锁的性能上要高。在ReentrantLock内部,如果有线程试图获取锁,且锁当前未被其他线程持有,那么无论等待队列中是否有线程在等待,该线程都能立即获取到锁,即 " 插队"。

实现原理

公平锁的实现与非公平锁实现原理类似,只不过在加锁之后,直接就调用了acquire(1)方法,而非公平锁再进入acquire(1)方法之前还会去尝试加锁。

注意

  1. 在等待队列中的线程时,一定是唤醒下标为1的,因为0是记录当前获取锁的线程
  2. 非公平的实现,只针对于线程入队之前,线程入队后还是会遵循队列的先进先出原则

使用场景

  • 多线程并发控制
  • 超时等待
  • 可中断锁实现
  • 公平锁
  • 多条件锁实现
相关推荐
街灯L4 分钟前
【Linux】Tomcat搭建
java·linux·服务器·tomcat
不像程序员的程序媛6 分钟前
es按时间 小时聚合
java·前端·elasticsearch
第1缕阳光17 分钟前
JVM对象创建全流程解析
java·jvm
一语长情38 分钟前
关于Netty的DefaultEventExecutorGroup使用
java·后端·架构
小巫程序Demo日记1 小时前
Spark DAG、Stage 划分与 Task 调度底层原理深度剖析
java·spark
Java中文社群1 小时前
ChatClient vs ChatModel:开发者必须知道的4大区别!
java·人工智能·后端
刘一说1 小时前
资深Java工程师的面试题目(五)微服务
java·微服务·面试
boy快快长大1 小时前
【JUC】显示锁
java
写bug写bug2 小时前
Spring Cloud中的@LoadBalanced注解实现原理
java·后端·spring cloud
青木川崎2 小时前
java获取天气信息
java