Lock -- 04 -- ReentrantLock底层实现原理

原文链接:Lock – 04 – ReentrantLock底层实现原理


相关文章:


ReentrantLock (重入锁) 是 Lock 接口最常见的一种实现,它与 synchronized 一样是可重入的,在基本语法上,ReentrantLock 也与 synchronized 很相似,只是代码写法上稍有区别而已

ReentrantLock 相比 synchronized 增加了一些高级功能,主要有以下三项

  • 等待可中断

    • 是指当有锁的线程长期不释放锁的时候,正常等待的线程可以选择放弃等待,改为处理其他事情,

    • 可中断特性对处理执行时间非常长的同步块很有帮助

  • 公平锁

    • 是指多个线程在等待同一个锁时,必须要按照申请锁的时间顺序来依次获得锁

    • 非公平锁不能保证这一点,在锁被释放时,任何一个等待锁的线程都有机会获得锁,synchronized 中的锁就是典型非公平锁

    • ReentrantLock 默认为非公平锁,可以通过带布尔值的构造函数来使用公平锁,不过使用公平锁后,将会导致 ReentrantLock 的性能急剧下降,会明显影响吞吐量

  • 锁绑定多个条件 (选择性通知)

    • 是指一个 ReentrantLock 对象可以同时绑定多个 Condition 对象

    • 在 synchronized 中,锁对象的 wait() 方法和 notify() / notifyAll() 方法相结合可以实现等待 / 通知机制;在 ReentrantLock 中,则需要借助 Condition 接口和 newCondition() 方法来实现

    • Condition 是在 JDK5 中引入的,具有很好的灵活性,比如可以实现多路通知功能,及在一个 Lock 对象中创建多个 Condition 实例 (即对象监视器),线程对象可以注册在指定的 Condition 中,从而可以有选择性的进行线程通知,在线程调度上更加灵活。而在 synchronized 中使用 notify() / notifyAll() 方法进行通知时,被通知的线程是有 JVM 选择的。因此用 ReentrantLock 类结合 Condition 实例可以实现"选择性通知",由 Condition 接口默认提供

    • synchronized 相当于整个 Lock 对象中只有一个 Condition 实例,所有线程都注册在它身上,如果执行 notifyAll() 方法的话,就会通知所有处于等待状态的线程,而 Condition 实例的 signalAll() 方法只会唤醒注册在该 Condition 实例中的所有等待线程


一、源码分析

  • ReentrantLock 位于 Java 并发包 (JUC) 中,实现了 Lock 和 Serializable 接口,其内部有一个实现锁功能的关键成员变量 sync (Sync 类型,是继承于 AbstractQueuedSynchronizer (AQS) 的内部抽象类),Sync 在 ReentrantLock 中有两个子类: NonfairSync 和 FairSync,即非公平锁和公平锁

  • ReentrantLock 通过 lock() 和 unlock() 两个方法来实现加锁和解锁

    • lock() 方法获取锁的主要流程 (NonfairSync 和 FairSync 中有不同的具体实现)

      • 首先,ReentrantLock 在 lock() 方法中调用了其成员变量 sync 的 lock() 方法 (这是一个抽象方法)

        public void lock() {
            sync.lock();
        }
        
      • 接着,在 NonfairSync 和 FairSync 两个 Sync 的子类中,覆盖了父类的 lock() 方法,并都会调用 Sync 父类 AbstractQueuedSynchronizer (AQS) 中的 acquire() 方法

        public final void acquire(int arg) {
            if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                selfInterrupt();
        }
        
        • 然后,调用 tryAcquire(arg) 方法去获取独占锁,若获取失败,则会调用 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 方法将请求获取锁的线程 (当前线程) 加入到等待队列中 (确切说应该是一个包含了当前线程的 Node 节点),直到其他线程释放锁,由请求获取锁的线程获取并返回;如果加入队列失败,则会返回 true,进而调用 selfInterrupt() 方法来中断当前线程

        • Node.EXCLUSIVE 标记表示当前节点正处于独占模式,与之对应的是 Node.SHARED 共享模式

        • addWaiter(Node.EXCLUSIVE), arg) 方法用于构造一个 Node 节点 (其构造方法中包含了当前线程),再由 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 方法将该节点加入到等待队列中等待,直到其他线程释放锁,由请求获取锁的线程获取并返回

    • unlock() 方法释放锁的主要流程

      • ReentrantLock 在 unlock() 方法中调用了其成员变量 sync 的 release(1) 方法,即调用的是 AQS 的 release(int arg) 方法

        public void unlock() {
            sync.release(1);
        }
        
      • AQS 的 release(int arg) 方法

        public final boolean release(int arg) {
            if (tryRelease(arg)) {
                Node h = head;
                if (h != null && h.waitStatus != 0)
                    unparkSuccessor(h);
                return true;
            }
            return false;
        }
        
        • AQS 的 release(int arg) 方法中会先调用 tryRelease(arg) 方法,即调用了 Sync 自身的 tryRelease(int releases) 方法

          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;
          }
          
          • 由上可知,在 tryRelease(int releases) 方法中,会先计算当前锁的状态 c,接着再判断当前线程是否是拥有独占锁的线程,如果不是则抛出 IllegalMonitorStateException 异常,然后再判断锁状态 c 的值,如果为 0,则会将拥有独占锁的线程重置为 null,并返回 true;如果不为 0,则返回 false
        • 接着会获取等待队列中头部节点 h,然后判断该节点 h 是否不为 null 且 waitStatus 标识是否不为 0,两者都为 true 时,则会调用 unparkSuccessor(h) 方法来唤醒该节点去获取独占锁

          • waitStatus 为 Node 节点状态标识,有以下五个值 (负值表示当前节点处于有效等待状态,而正值表示当前节点已被取消,所以源码中很多地方用 >0 或 <0 来判断当前节点的状态是否正常)

            • CANCELLED (1)

              • 表示当前节点中的线程已被取消

              • 当线程等待超时或被中断时,会触发变更为此状态,进入该状态的节点不会再发生变化,且节点中的线程永远不会再被阻塞

            • SIGNAL (-1)

              • 表示后继节点正在等待当前节点的唤醒

              • 当节点释放锁或被取消时,会唤醒其后继节点,使后继节点中的线程得以运行

            • CONDITION (-2)

              • 表示当前节点等待在 Condition 上

              • 当其他线程调用了 Condition 的 signal() 方法后,该节点会从等待队列转移到同步队列中,等待获取同步锁

            • PROPAGATE (-3)

              • 表示下一次的共享状态会被无条件的传播下去

              • 在共享模式下,当前节点不仅会唤醒其后继节点,同时也可能会唤醒后继的后继节点

            • 0

              • 新节点加入队列时的默认状态

二、非公平锁

  • NonfairSync 继承自 Sync,并覆盖了 Sync 的 lock() 方法,同时还覆盖了 AbstractQueuedSynchronizer 的 tryAcquire() 方法

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
    
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
    
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }
    
    • 由上可知,NonfairSync 的 lock() 方法中,直接使用了基于 CAS 操作的 compareAndSetState(0, 1) 方法来获取锁,若 CAS 操作成功,则会将当前线程设置为该锁的唯一拥有者

      • AQS 中维护了一个锁状态字段 state,由 volatile 进行修饰,其中 0 表示未被获取,1 表示已被获取, 大于 1 表示重入数

      • 当调用 compareAndSetState(0, 1) 方法来获取锁时,即尝试将锁状态 state0 变为 1,若操作成功,则其他线程调用 compareAndSetState(0, 1) 方法时就会失败,直到当前线程释放锁为止

    • 若 CAS 操作失败,则会调用 AQS 的 acquire(1) 方法,即调用了 NonfairSync 自身的 tryAcquire(int acquires) 方法来获取独占锁,若获取失败,则会调用 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 方法将请求获取锁的线程加入到等待队列中等待,直到其他线程释放锁,由请求获取锁的线程获取并返回

    • tryAcquire(int acquires) 方法中又调用了 Sync 的 nonfairTryAcquire(int 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;
      }
      
      • 由上可知,如果锁状态 (state) 为 0 时,会进行 CAS 操作来获取独占锁,若操作成功,则将当前线程设置为该锁的唯一拥有者并返回 true;如果持有锁的线程为当前拥有独占锁的线程,则通过累加状态标识 nextc 来记录重入次数,并返回 true;若都不是,则返回 false

三、公平锁

  • FairSync 继承自 Sync,并覆盖了 Sync 的 lock() 方法,同时还覆盖了 AbstractQueuedSynchronizer 的 tryAcquire() 方法

    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
    
        final void lock() {
            acquire(1);
        }
    
        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;
        }
    }
    
    • 由上可知,FairSync 的 lock() 方法中,仅调用了 AQS 的 acquire(1) 方法,即调用了 FairSync 自身的 tryAcquire(int acquires) 方法来获取独占锁,若获取失败,则会调用 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 方法将请求获取锁的线程加入到等待队列中

    • tryAcquire(int acquires) 方法中,如果锁状态 (state) 为 0 时,会先通过 hasQueuedPredecessors() 方法来判断等待队列中有没有比当前线程等待时间更长的线程 (即优先级更高),如果没有,则再进行 CAS 操作来获取独占锁,若操作成功,则将当前线程设置为该锁的唯一拥有者并返回 true;如果持有锁的线程为当前拥有独占锁的线程,则通过累加状态标识 nextc 来记录重入次数,并返回 true;若都不是,则返回 false

      public final boolean hasQueuedPredecessors() {
          // The correctness of this depends on head being initialized
          // before tail and on head.next being accurate if the current
          // thread is first in queue.
          Node t = tail; // Read fields in reverse initialization order
          Node h = head;
          Node s;
          return h != t &&
              ((s = h.next) == null || s.thread != Thread.currentThread());
      }
      

四、代码示例

  • 等待可中断

    public class LockInterruptiblyTest{
    
        private Lock lock = new ReentrantLock();
    
        public void lockTest() {
            String threadName = Thread.currentThread().getName();
            try {
                System.out.println("线程【" + threadName + "】:我来获取锁了");
                // lock.lock();
                lock.lockInterruptibly();
                System.out.println("线程【" + threadName + "】:我获取到锁了,要准备干活了!");
                long start = System.currentTimeMillis();
                while (true) {
                    long end = System.currentTimeMillis();
                    if ((end - start) > 10000L) {
                        break;
                    }
                }
                System.out.println("线程【" + threadName + "】:好累啊,干了10秒钟的活");
            } catch (Exception e) {
                System.out.println("线程【" + threadName + "】:啊,我被中断了!");
                e.printStackTrace();
            } finally {
                lock.unlock();
                System.out.println("线程【" + threadName + "】:收工回家,我要释放锁了");
            }
        }
    
        public static void main(String[] args) throws Exception {
            LockInterruptiblyTest test = new LockInterruptiblyTest();
            Thread thread1 = new Thread(() -> test.lockTest(), "thread1");
            Thread thread2 = new Thread(() -> test.lockTest(), "thread2");
            System.out.println("主线程:我来启动线程【thread1】了");
            thread1.start();
            System.out.println("主线程:好累啊,先睡个3秒钟,再去启动线程【thread2】吧");
            TimeUnit.SECONDS.sleep(3);
            System.out.println("主线程:我来启动线程【thread2】了");
            thread2.start();
            System.out.println("线程【thread2】:诶呀,获取不到锁,先睡个3秒钟吧,然后再看看情况");
            TimeUnit.SECONDS.sleep(3);
            System.out.println("线程【thread2】:都睡了3秒钟了,还获取不到锁,不干了,我要中断了!");
            thread2.interrupt();
        }
    }
    
    • 使用 lockInterruptibly() 方法输出如下

      // 主线程:我来启动线程【thread1】了
      // 主线程:好累啊,先睡个3秒钟,再去启动线程【thread2】吧
      // 线程【thread1】:我来获取锁了
      // 线程【thread1】:我获取到锁了,准备干活了!
      // 主线程:我来启动线程【thread2】了
      // 线程【thread2】:诶呀,获取不到锁,先睡个3秒钟吧,然后再看看情况
      // 线程【thread2】:我来获取锁了
      // 线程【thread2】:都睡了3秒钟了,还获取不到锁,不干了,我要中断了!
      // 线程【thread2】:啊,我被中断了!
      // java.lang.InterruptedException
      //     at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
      //     at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
      //     at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
      //     at com.xj.thread.LockInterruptiblyTest.lockTest(LockInterruptiblyTest.java:21)
      //     at com.xj.thread.LockInterruptiblyTest.lambda$main$1(LockInterruptiblyTest.java:43)
      //     at java.lang.Thread.run(Thread.java:748)
      // Exception in thread "thread2" java.lang.IllegalMonitorStateException
      //     at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
      //     at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
      //     at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
      //     at com.xj.thread.LockInterruptiblyTest.lockTest(LockInterruptiblyTest.java:35)
      //     at com.xj.thread.LockInterruptiblyTest.lambda$main$1(LockInterruptiblyTest.java:43)
      //     at java.lang.Thread.run(Thread.java:748)
      // 线程【thread1】:好累啊,干了10秒钟的活
      // 线程【thread1】:收工回家,我要释放锁了
      
    • 使用 lock() 方法输出如下

      // 主线程:我来启动线程【thread1】了
      // 主线程:好累啊,先睡个3秒钟,再去启动线程【thread2】吧
      // 线程【thread1】:我来获取锁了
      // 线程【thread1】:我获取到锁了,准备干活了!
      // 主线程:我来启动线程【thread2】了
      // 线程【thread2】:诶呀,获取不到锁,先睡个3秒钟吧,然后再看看情况
      // 线程【thread2】:我来获取锁了
      // 线程【thread2】:都睡了3秒钟了,还获取不到锁,不干了,我要中断了!
      // 线程【thread1】:好累啊,干了10秒钟的活
      // 线程【thread1】:收工回家,我要释放锁了
      // 线程【thread2】:我获取到锁了,准备干活了!
      // 线程【thread2】:好累啊,干了10秒钟的活
      // 线程【thread2】:收工回家,我要释放锁了
      
    • 如上所示,当某个线程使用 lock() 方法来获取锁时,如果锁已被另外的线程获取,则该线程会处于等待状态,若此时调用该线程的 interrupt() 方法,则会将该线程的中断标识设为 true,该线程会继续执行,不受影响,何时中断取决于该线程本身

    • 当某个线程使用 lockInterruptibly() 方法来获取锁时,如果锁已被另外的线程获取,则该线程会处于等待状态,若此时调用该线程的 interrupt() 方法,则会中断该线程的等待而直接返回,并抛出 InterruptedException 异常

  • 公平锁

    public class FairLock implements Runnable {
    
        private static ReentrantLock fairLock = new ReentrantLock(true);
    
        @Override
        public void run() {
            IntStream.range(0, 5).forEach(i -> {
                try {
                    fairLock.lock();
                    System.out.println(Thread.currentThread().getName() + " get lock");
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    fairLock.unlock();
                }
            });
        }
    
        public static void main(String[] args) {
            FairLock fairLock = new FairLock();
            Thread thread1 = new Thread(fairLock);
            Thread thread2 = new Thread(fairLock);
            thread1.start();
            thread2.start();
        }
    }
    
    // Thread-1 get lock
    // Thread-0 get lock
    // Thread-1 get lock
    // Thread-0 get lock
    // Thread-1 get lock
    // Thread-0 get lock
    // Thread-1 get lock
    // Thread-0 get lock
    // Thread-1 get lock
    // Thread-0 get lock
    
    • 如上所示,在使用了公平锁的情况下,线程 1 和线程 2 交替获取独占锁
  • 非公平锁

    public class NonFairLock implements Runnable {
    
        private static ReentrantLock nonFairLock = new ReentrantLock(false);
    
        @Override
        public void run() {
            IntStream.range(0, 5).forEach(i -> {
                try {
                    nonFairLock.lock();
                    System.out.println(Thread.currentThread().getName() + " get lock");
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    nonFairLock.unlock();
                }
            });
        }
    
        public static void main(String[] args) {
            NonFairLock nonFairLock = new NonFairLock();
            Thread thread1 = new Thread(nonFairLock);
            Thread thread2 = new Thread(nonFairLock);
            thread1.start();
            thread2.start();
        }
    }
    
    // Thread-1 get lock
    // Thread-1 get lock
    // Thread-1 get lock
    // Thread-1 get lock
    // Thread-1 get lock
    // Thread-0 get lock
    // Thread-0 get lock
    // Thread-0 get lock
    // Thread-0 get lock
    // Thread-0 get lock
    
    • 如上所示,在使用了非公平锁的情况下,线程 1 和线程 2 竞争获取独占锁
  • 锁绑定多个条件 (选择性通知)

    public class LockCondition {
    
        private int count = 0;
        private boolean isOk = false;
        private Lock lock = new ReentrantLock();
        private Condition producer = lock.newCondition();
        private Condition consumer = lock.newCondition();
    
        public void product() {
            try {
                lock.lock();
                while (true) {
                    if (isOk) {
                        producer.await();
                    }
                    count++;
                    isOk = true;
                    System.out.println("producer【" + count + "】生产数据");
                    TimeUnit.SECONDS.sleep(1);
                    consumer.signal();
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        public void consume() {
            try {
                lock.lock();
                while (true) {
                    if (!isOk) {
                        consumer.await();
                    }
                    isOk = false;
                    System.out.println("consumer【" + count + "】消费数据");
                    TimeUnit.SECONDS.sleep(1);
                    producer.signal();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            LockCondition lockCondition = new LockCondition();
            Thread producer1 = new Thread(new ProducerThread(lockCondition));
            Thread consumer1 = new Thread(new ConsumerThread(lockCondition));
            Thread producer2 = new Thread(new ProducerThread(lockCondition));
            Thread consumer2 = new Thread(new ConsumerThread(lockCondition));
            producer1.start();
            consumer1.start();
            producer2.start();
            consumer2.start();
        }
    }
    
    class ProducerThread implements Runnable {
    
        private LockCondition producer;
    
        public ProducerThread(LockCondition producer) {
            this.producer = producer;
        }
    
        @Override
        public void run() {
            producer.product();
        }
    }
    
    class ConsumerThread implements Runnable {
    
        private LockCondition consumer;
    
        public ConsumerThread(LockCondition consumer) {
            this.consumer = consumer;
        }
    
        @Override
        public void run() {
            consumer.consume();
        }
    }
    
    • 如上所示,我们通过两个 Condition 对象来分别控制生产线程和消费线程,当生产线程生产数据后,消费线程才能消费数据,且不能重复消费

    • 在 main 方法中,我们创建了两个生产线程和两个消费线程进行数据模拟,由结果可知,符合预期,避免了消费线程在唤醒线程时还是唤醒消费线程的情况 (此处调用 producer.signal(),只会唤醒两个生产线程中的其中一个)

    • 因此通过 ReentrantLock + Condition 的组合,我们可以实现线程的选择性通知


五、参考资料

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值