• Java ReentrantLock锁源码走读


    多线程例子程序:两个线程累加共享变量,结果正确

    public class Test {
    
        static int count = 0;
        static ReentrantLock lock = new ReentrantLock();
    
        public static void main(String[] args) throws InterruptedException {
    
            Runnable runnable = new Runnable() {
                @Override
                public void run () {
                    try {
                        lock.lock();
                        for (int i = 0; i < 10000; i++) {
                            count++;
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        lock.unlock();
                    }
                }
            };
    
            Thread thread1 = new Thread(runnable);
            Thread thread2 = new Thread(runnable);
            thread1.start();
            thread2.start();
            thread1.join();
            thread2.join();
    
            System.out.println(count);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    非公平锁加锁(即 lock.lock();)过程

    在这里插入图片描述

    1. ReentrantLock的lock方法 走到 同步器的 acquire(1)方法
    2. 走到aqs的acquire的方法,而其tryAcquire方法实现则是在NonfairSync类中
    3. NonfairSync执行tryAcquire方法又回到Sync的nonfairTryAcquire方法
    4. Sync的nonfairTryAcquire方法则如下

    相关代码 java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire

    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;
    
        /**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
        @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()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    1. 进行cas自旋获取,成功则设置当前线程获取到锁(此时state=1),否则一直自旋。
    2. 如果当前线程就是获取到锁的线程,则为可重入,此时 state > 1
    3. 其中 private volatile int state; volatile保证可见性、有序性,不保证原子性(CAS操作保证原子性)

    可以看到

    1. 当一个线程lock获取到锁后成功返回,并执行自己的代码(独占模式)
    2. 其它线程获取不到锁,则一直自旋在哪里

    非公平锁解锁( lock.unlock();)过程

    释放锁则相对简单,如下图所示

    在这里插入图片描述

    相关代码java.util.concurrent.locks.ReentrantLock.Sync#tryRelease

    @ReservedStackAccess
     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;
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    公平锁

    从上面非公平锁可以看到,加锁操作时看哪个线程cas抢到就行,至于谁抢到无所谓。可能有线程永远抢不到,即线程饥饿。这就出现了不公平的现象了

    new ReentrantLock(true);就得到了一个公平锁

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

    公平锁的加锁逻辑

    体现在java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire

    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()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    1. 会先判断当前线程前面是否有等待的线程,即利用asq的等待队列进行判断

      • 如果有,则自己加入到链表尾部;返回false
      • 如果队列为空或者自己就是链表头;返回true
    2. hasQueuedPredecessors返回false,则可以进行cas获取锁操作了

    3. hasQueuedPredecessors返回true,那么自己则要排队了,先来后到(公平),不用cas操作了,直接加锁失败

    公平锁的释放锁逻辑

    同非公平锁

  • 相关阅读:
    Oracle使用遇到的问题
    mysql 安装问题 perl(JSON) is needed by mysql-community-test
    快速全面掌握数据库系统核心知识点
    2022-2023年度的AMC数学竞赛报名时间来了
    YOLOv8训练自己的数据集,十秒学会!小白一文学会YOLOv8训练全过程!适应于小白
    查看redis的Value值大小
    初始Go--基本数据结构
    【最新】如何将idea上的项目推送到gitee
    全新的FL studio21.2版支持原生中文FL studio2024官方破解版
    SPI通信
  • 原文地址:https://blog.csdn.net/qq_26437925/article/details/132920787