Java进阶:ReentrantLock实现原理解析(公平锁、非公平锁、可重入锁、自旋锁)

概述

本篇将介绍公平锁、非公平锁、可重入锁、自旋锁相关理论知识,同时结合相关源码和Demo进行解析,主要是以ReentrantLock作为例子。

公平锁

公平锁定义

公平锁是指线程按照申请所的顺序来获取锁,跟队列先进先出一样,排队按顺序获取锁
在这里插入图片描述

公平锁源码解析

1. 首先,看下ReentrantLock构造函数:

    public ReentrantLock() {
    
    
        sync = new NonfairSync();
    }
    public ReentrantLock(boolean fair) {
    
    
        sync = fair ? new FairSync() : new NonfairSync();
    }
  • 可见,ReentrantLock默认是非公平锁 NonfairSync
  • 构造函数支持传入参数 fair,假设我们现在传入true,就会创建公平锁 FairSync

2. FairSync源码:

	final void lock() {
    
    
    	acquire(1);
    }
  • lock 就是我们调用的加锁方法,内部调用acquire,记住这个参数值1,后面会用到

3. acquire源码:

    public final void acquire(int arg) {
    
    
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
  • tryAcquire:此处尝试获取锁,true获取成功并且return,false获取失败并执行下一步代码

在这里插入图片描述

  • addWaiter:给等待队列添加一个节点,标记当前线程正在等待获取锁,返回当前添加的节点Node
    锁的队列
  • acquireQueued:死循环(自旋锁)的方式,等待获取到锁资源,成功获取了才return
    在这里插入图片描述

非公平锁

非公平锁定义

  • 非公平锁指的是多个线程获取锁的顺序不一定是申请的顺序,有可能后面申请的线程反而比前面的线程优先获取。
  • 非公平锁在申请锁资源时,会先尝试占有锁,成功了就独占锁资源,失败了就进入排队队列

在这里插入图片描述

  • 高并发环境下,由于存在抢占锁的机制,可能会造成优先级反转或者饥饿现象。也就是说,原本排在第2的线程,顺序变成滞后。或者存在有的线程,长时间一直获取不到锁。

非公平锁源码解析

扫描二维码关注公众号,回复: 11929908 查看本文章
  1. 看下NonfairSync.lock方法:
    final void lock() {
    
    
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
  • compareAndSetState:尝试以CAS方式设置state=1,也就是抢先占有锁,成功了就设置独占锁的线程为当前线程
  • acquire:上一步抢占锁失败了,就执行该方法。acquire跟公平锁源码一样,这里不贴出来了,有差别的是tryAcquire方法。
  1. tryAcquire源码:
    protected final boolean tryAcquire(int acquires) {
    
    
        return nonfairTryAcquire(acquires);
    }
    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;
    }
  • 整个方法跟公平锁的基本一样,不同点是少了hasQueuedPredecessors方法。也就是说,不会先判断队列是否有等待线程,先占用再说,失败了再进入线程等待。

可重入锁(也叫作递归锁)

可重入锁定义

  • 可重入锁指的是同一个线程获得锁之后,可以再次获得锁而不会发生死锁
  • 加锁的次数和解锁的次数必须相同,ReentrantLock需要手动加锁解锁,synchronized会自动解锁

可重入锁Demo

public class 可重入锁App
{
    
    
    private synchronized static void sendSMS(){
    
    
        System.out.println(Thread.currentThread().getName() + " sendSMS");
        sendEmail();
    }

    private synchronized static void sendEmail(){
    
    
        System.out.println(Thread.currentThread().getName() + " sendEmail");
    }

    public static void main( String[] args )
    {
    
    
        new Thread(() -> {
    
    
            sendSMS();
        }, "Test-1").start();
    }
}
  • 定义了2个加锁方法:sendSMS、sendEmail。其中,sendSMS内部又调用了sendEmail
  • 创建个线程执行sendSMS,可以看到2个方法都能正常调用,不会产生堵塞
输出结果:
Test-1 sendSMS
Test-1 sendEmail
Test-2 sendSMS
Test-2 sendEmail

自旋锁

自旋锁定义

  • 当某个线程尝试获取锁资源失败,会无限次数地再次获取,直到成功为止
  • 缺点是循环会消耗CPU资源
    在这里插入图片描述

自旋锁Demo

公平锁的acquireQueued方法,==for( ; ; ){ }==就是自旋锁的方式

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_28834355/article/details/108690540