Java 并发之AQS与ReentrantReadWriteLock

683 阅读3分钟

书接上文

ReentrantReadWriteLock样例

public class ReadWriteLockTest {
    public DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss:SSS");
    public ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    public  String getTime(){
        return LocalDateTime.now(ZoneOffset.of("+8")).format(formatter);
        //字符串转时间String dateTimeStr = "2018-07-28 14:11:15";
    }
    public void readLockTest(){
        try {
               readWriteLock.readLock().lock();
               System.out.println("start readLockTest at "+getTime());
               Thread.sleep(2000);
               System.err.println("end readLockTest at "+getTime());
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            readWriteLock.readLock().unlock();
        }
    }
    public void writeLockTest(){
        try {
            readWriteLock.writeLock().lock();
            System.out.println("start writeLockTest at "+getTime());
            Thread.sleep(2000);
            System.err.println("end writeLockTest at "+getTime());
        }catch (InterruptedException e){
            e.printStackTrace();
        }finally {
            readWriteLock.writeLock().unlock();
        }
    }

    public static void main(String[] args) {
       /*
        a << b就表示把a转为二进制后左移b位(在后面添b个0)。
        a >> b表示二进制右移b位(去掉末b位).
       */
        ReadWriteLockTest readWriteLockTest = new ReadWriteLockTest();
        IntStream.range(0,4).forEach(i-> new Thread(readWriteLockTest::readLockTest).start());
        IntStream.range(0,4).forEach(i-> new Thread(readWriteLockTest::writeLockTest).start());
    }
}

image.png

不厌其烦的阅读jdk源码

ReadWriteLock接口

image.png

其中一个实现类ReentrantReadWriteLock

构造方法和成员变量

image.png

image.png

WriteLock的构造类似,不再赘述

AQS在ReentrantReadWriteLock中的角色

image.png

ReentrantReadWriteLock的ReadLock获取锁的源码分析

image.png

ReentrantReadWriteLock的WriteLock获取锁的源码分析

image.png

Sync有两个版本

image.png

***ShouldBlock(),在获取锁的tryAcquire()方法中有调用

小结——关于ReentrantReadWriteLock获取锁的操作逻辑:

  • 读锁:
    • 在获取读锁时,会尝试判断当前对象是否拥有了写锁,如果已经拥有,则直接获取失败。
    • 如果没有写锁,就表示当前对象没有排他锁(写锁),则当前线程会尝试给对象加锁。
    • 如果当前线程已经持有了该对象的锁,那么直接将读锁数量加1(可重入)。
  • 写锁:
    • 在获取写锁时,会尝试判断当前对象是否拥有了锁(读锁与写锁),只能选其一。如果已经拥有且持有锁的线程并非当前线程,直接获取失败。
    • 如果当前对象没有被加锁,那么写锁就会为当前对象上锁,并且将写锁的个数加1(可重入)。
    • 将当前对象的排他锁线程持有者设为自己(setExclusiveOwnerThread(current));

ReentrantReadWriteLock的ReadLock释放锁的源码分析

image.png

ReentrantReadWriteLock的WriteLock释放锁的源码分析

image.png

关于公平锁和非公平锁

  • 公平锁就是严格的FIFO。
  • 在非公平锁的实现中,线程在尝试获取锁时仍然会先去查看队列头节点的状态,如果头节点是释放锁的状态,则当前线程直接获取锁,而不需要进入队列等待。只有当头节点是占用锁的状态时,当前线程才会进入队列排队等待锁的释放。因此,队列中的节点仍然会被考虑,只是在唤醒线程时可能会考虑头节点外的其他节点。 因此,非公平锁可以在一定程度上提高系统的吞吐量,但它仍然会考虑队列中的节点,而不是完全不考虑。

为什么需要可重入锁

  • 不可重入锁,极其容易发生死锁。
    // 定义一个不可重入锁类
    class NonReentrantLock {
        private boolean isLocked = false;
    
        public synchronized void lock() throws InterruptedException {
          while (isLocked) {
              wait();
          }
          isLocked = true;
        }
    
        public synchronized void unlock() {
           isLocked = false;
           notify();
        }
    }
    
    // 定义一个测试类
    class Test {
        NonReentrantLock lock = new NonReentrantLock();
    
        public void methodA() throws InterruptedException {
           lock.lock(); // 获取锁
           System.out.println("执行方法A");
           methodB(); // 调用方法B
           lock.unlock(); // 释放锁
        }
    
        public void methodB() throws InterruptedException {
           lock.lock(); // 尝试获取锁,但会被阻塞,因为锁已经被当前线程持有
           System.out.println("执行方法B");
           lock.unlock(); // 释放锁
        }
    }
    
    // 定义一个主类
    public class Main {
        public static void main(String\[] args) throws InterruptedException {
           Test test = new Test();
           test.methodA(); // 调用方法A,会发生死锁
        }
    }
    
    • 因为methodB()无法获取到锁,它就没法执行。导致methodA()迟迟无法结束,遂死锁。