背景介绍

在JDK1.5之后提供了在协调对共享对象访问时的机制。它提供了一种无条件的、可轮询的、定时的以及可中断的锁获取操作,所有加锁和解锁的语法都是显示的。它是基于AQS实现的并发访问控制

  1. private static int count = 0;
  2. public static void main(String[] args) throws InterruptedException {
  3. Lock lock = new ReentrantLock();
  4. Thread t1 = new Thread(new CountUtil(lock));
  5. Thread t2 = new Thread(new CountUtil(lock));
  6. t1.start();
  7. t2.start();
  8. t1.join();
  9. t2.join();
  10. System.out.println(count);
  11. }
  12. static class CountUtil implements Runnable{
  13. private final Lock lock;
  14. public CountUtil(Lock lock) {
  15. this.lock = lock;
  16. }
  17. @Override
  18. public void run() {
  19. lock.lock();
  20. for (int i = 0; i < 100; i++) {
  21. count++;
  22. }
  23. lock.unlock();
  24. }
  25. }

重入性

ReentrantLock是一个可重入锁。即同一个线程是可以循环加锁的

  1. for (int i = 0; i < 100; i++) {
  2. lock.lock();
  3. count++;
  4. lock.unlock();
  5. }

公平性

公平性是针对获取锁而言的,如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是FIFO。当构造函数中的参数为true时,即为公平锁

  1. new ReentrantLock(true)

指定时间内尝试加锁

ReentrantLock可以指定一段时间内尝试加锁

  1. Lock lock = new ReentrantLock();
  2. boolean result = lock.tryLock(1, TimeUnit.SECONDS);

ReentrantLock与Synchronized区别

  1. 尝试非阻塞地获取锁:当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并尺有锁
  2. 能被中断地获取锁:与synchronized不同,获取到锁的线程能够相应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁会被释放
  3. 超时获取锁,在指定的截止时间之前获取锁,如果截止时间到了仍无法获取锁,则返回

    加锁原理

    核心变量

    ReentrantLock底层是基于AQS实现的并发访问控制。AQS的核心成员主要有state(状态)、exclusiveOwnerThread(当前加锁线程)和阻塞队列。
    截屏2021-01-20 上午11.00.00.png

    加锁逻辑

    非公平锁

    线程直接通过cas尝试修改state变量(对其进行加1操作),如果成功则同时将exclusiveOwnerThread(当前加锁线程)设置为自己。如果cas修改state失败,则需要判断当前加锁的线程是否是自己,如果是,对state进行+1操作(可重入,多次进入就是对state+1),如果不是进入阻塞队列等待

    公平锁

    线程在加锁前需要先判断阻塞队列中是否有等待中的线程,如果有就进入阻塞队列,如果没有再通过cas的方式加锁

截屏2021-01-20 上午11.32.52.png

释放锁

成功加锁的线程每次进入时都会对state进行+1,所以每次释放时就是对state进行-1,如果state=0时则彻底释放锁,并唤起阻塞队列中的头结点进行获取锁

超时获取锁

截屏2021-01-20 下午2.46.54.png