第十三章Java锁—进阶

AQS原理

概述

  • 全称是 AbstractQueuedSynchronizer,是阻塞式锁相关的同步器工具的框架,及整个JUC体系的基石
    • 锁,面向锁的使用者(定义了程序员和锁交互的使用层API,隐藏了实现细节,你调用即可)
      • 加锁会导致阻塞、有阻塞就需要排队,实现排队必然需要队列
    • 同步器,面向锁的实现者(比如Java并发大神Douglee,提出统一规范并简化了锁的实现,屏蔽了同步状态管理、阻塞线程排队和通知、唤醒机制等。)

img

  • 用 state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁
    • getState - 获取 state 状态
    • setState - 设置 state 状态
    • compareAndSetState - cas 机制设置 state 状态
    • 独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源
  • 通过内置的CLH(FIFO)队列的变种来完成资源获取线程的排队工作,类似于 Monitor 的 EntryList
    • 将每条将要去抢占资源的线程封装成一个Node节点来实现锁的分配
    • CLH:Craig、Landin and Hagersten 队列,是一个单向链表,AQS中的队列是CLH变体的虚拟双向队列FIFO
  • 条件变量(Condition)来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor 的 WaitSet

AQS 要实现的功能目标

  • 阻塞版本获取锁 acquire 和非阻塞的版本尝试获取锁 tryAcquire
  • 获取锁超时机制
  • 通过打断取消机制
  • 独占机制及共享机制
  • 条件不满足时的等待机制

AQS内部有什么

image-20230701131759000 image-20230701140839181
  • AQS的同步状态State成员变量,用volatile修饰,保证了可见性
    • 类比银行办理业务的受理窗口状态
      • 0表示没人,自由状态可以办理
      • 大于等于1,有人占用窗口,等着去
  • AQS的CHL队列
    • 类比银行客区的等待客户
  • 内部类Node(Node类在AQS类内部)
    • volatile int waitStatus 表示Node的等待状态
static final class Node {
    
    
        static final Node SHARED = new Node();
        static final Node EXCLUSIVE = null;
        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;    
        volatile int waitStatus;
        volatile Node prev;
        volatile Node next;
        volatile Thread thread;
        Node nextWaiter;
}
在这里插入图片描述

AQS同步队列的基本结构

image-20230701145159588

AQS为什么是JUC内容中最重要的基石

ReentrantLock|CountDownLatch|ReentrantReadWriteLock|Semaphore,这些底层原理都是AQS

image-20230701130715029

ReentrantLock 基本使用

相对于 synchronized 它具备如下特点

  • 可中断
  • 可以设置超时时间
  • 可以设置为公平锁
  • 支持多个条件变量

与 synchronized 一样,都支持可重入

基本语法

// 获取锁
reentrantLock.lock();
try {
    
    
	// 临界区
} finally {
    
    
	// 释放锁
	reentrantLock.unlock();
}

对应特性和基本使用

可重入

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁,如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住

public class reentrantLockDemo {
    
    
    static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
    
    
        method1();
    }
    public static void method1() {
    
    
        lock.lock();
        try {
    
    
            System.out.println("execute method1");
            method2();
        } finally {
    
    
            lock.unlock();
        }
    }
    public static void method2() {
    
    
        lock.lock();
        try {
    
    
            System.out.println("execute method2");
            method3();
        } finally {
    
    
            lock.unlock();
        }
    }
    public static void method3() {
    
    
        lock.lock();
        try {
    
    
            System.out.println("execute method3");
        } finally {
    
    
            lock.unlock();
        }
    }
}
execute method1
execute method2
execute method3

可中断

public class reentrantLockDemo {
    static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"启动...");
            try {
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println(Thread.currentThread().getName()+"等锁的过程中被打断");
                return;
            }
            try {
                System.out.println(Thread.currentThread().getName()+"获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        System.out.println(Thread.currentThread().getName()+"获得了锁");
        t1.start();
        try {
           TimeUnit.SECONDS.sleep(1);
            t1.interrupt();
            System.out.println(Thread.currentThread().getName()+"执行打断");
        } finally {
            lock.unlock();
        }
    }
}
main获得了锁
t1启动...
main执行打断
t1等锁的过程中被打断
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.lsc.day12.reentrantLockDemo.lambda$main$0(reentrantLockDemo.java:20)
	at java.lang.Thread.run(Thread.java:748)
  • 注意如果是不可中断模式,那么即使使用了 interrupt 也不会让等待中断
public class reentrantLockDemo {
    
    
    static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread t1 = new Thread(() -> {
    
    
            System.out.println(Thread.currentThread().getName()+"启动...");
				
            lock.lock();
            try {
    
    
                System.out.println(Thread.currentThread().getName()+"获得了锁");
            } finally {
    
    
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        System.out.println(Thread.currentThread().getName()+"获得了锁");
        t1.start();
        try {
    
    
           TimeUnit.SECONDS.sleep(1);
            t1.interrupt();
            System.out.println(Thread.currentThread().getName()+"执行打断");
        } finally {
    
    
            lock.unlock();
        }
    }
}
main获得了锁
t1启动...
main执行打断
t1获得了锁

锁超时

    static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
    
    
        ReentrantLock lock = new ReentrantLock();
        Thread t1 = new Thread(() -> {
    
    
            System.out.println("启动...");
            try {
    
    
                if (!lock.tryLock(1, TimeUnit.SECONDS)) {
    
    
                    System.out.println("获取等待 1s 后失败,返回");
                    return;
                }
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            try {
    
    
                System.out.println("获得了锁");
            } finally {
    
    
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        System.out.println("获得了锁");
        t1.start();
        try {
    
    
           Thread.sleep(2000);
        } finally {
    
    
            lock.unlock();
        }
    }
获得了锁
启动...
获取等待 1s 后失败,返回

公平锁

ReentrantLock 默认是不公平的

扫描二维码关注公众号,回复: 16514251 查看本文章
 public static void main(String[] args) throws InterruptedException {
    
    
        ReentrantLock lock = new ReentrantLock(false);
        lock.lock();
        for (int i = 0; i < 500; i++) {
    
    
            new Thread(() -> {
    
    
                lock.lock();
                try {
    
    
                    System.out.println(Thread.currentThread().getName() + " running...");
                } finally {
    
    
                    lock.unlock();
                }
            }, "t" + i).start();
        }
        // 1s 之后去争抢锁
        Thread.sleep(1000);
        new Thread(() -> {
    
    
            System.out.println(Thread.currentThread().getName() + " start...");
            lock.lock();
            try {
    
    
                System.out.println(Thread.currentThread().getName() + " running...");
            } finally {
    
    
                lock.unlock();
            }
        }, "强行插入").start();
        lock.unlock();
    }
}
t39 running...
t40 running...
t41 running...
t42 running...
t43 running...
强行插入 start...
强行插入 running...
t44 running...
t45 running...
t46 running...
t47 running...
t49 running...

改为公平锁后

t465 running...
t464 running...
t477 running...
t442 running...
t468 running...
t493 running...
t482 running...
t485 running...
t481 running...
强行插入 running...
  • 公平锁一般没有必要,会降低并发度,后面分析原理时会讲解

条件变量

synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待

  • ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比
  • synchronized 是那些不满足条件的线程都在一间休息室等消息
  • 而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒

使用要点:

  • await 前需要获得锁
  • await 执行后,会释放锁,进入 conditionObject 等待
  • await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
  • 竞争 lock 锁成功后,从 await 后继续执行

重写之前wait/notify实现的需求

public class ConditionDemo {
    
    
    static ReentrantLock lock = new ReentrantLock();
    static Condition waitCigaretteQueue = lock.newCondition();
    static Condition waitBreakfastQueue = lock.newCondition();
    static volatile boolean hasCigrette = false;
    static volatile boolean hasBreakfast = false;

    public static void main(String[] args) throws InterruptedException {
    
    
        new Thread(()->{
    
    
            try {
    
    
                lock.lock();
                while (!hasBreakfast){
    
    
                    try {
    
    
                        waitBreakfastQueue.await();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
                System.out.println("得到了他的早餐");
            }finally {
    
    
                lock.unlock();
            }
        },"t1").start();
        new Thread(()->{
    
    
            try {
    
    
                lock.lock();
                while (!hasCigrette){
    
    
                    try {
    
    
                        waitCigaretteQueue.await();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
                System.out.println("得到了他的烟");
            }finally {
    
    
                lock.unlock();
            }
        },"t2").start();
        TimeUnit.SECONDS.sleep(1);
        sendBreakfast();
        TimeUnit.SECONDS.sleep(1);
        sendCigarette();
    }
    private static void sendCigarette() {
    
    
        lock.lock();
        try {
    
    
            System.out.println("送烟来了");
            hasCigrette = true;
            waitCigaretteQueue.signal();
        } finally {
    
    
            lock.unlock();
        }
    }
    private static void sendBreakfast() {
    
    
        lock.lock();
        try {
    
    
            System.out.println("送早餐来了");
            hasBreakfast = true;
            waitBreakfastQueue.signal();
        } finally {
    
    
            lock.unlock();
        }
    }
}

  • 之前我们的synchronized实现,是将两个线程都放到一个等待队列中,现在我们将不同需求的放入到不同的等待队列,能够精确唤醒对应的线程

ReentrantLock非公平锁原理解析

以ReentranLock的非公平锁来解析AQS

构造方法

public ReentrantLock() {
    
    
	sync = new NonfairSync();//默认是不公平
}
  • NonfairSync 继承自 AQS
image-20230701150703939

加锁方法

public void lock() {
    
    
     sync.lock();
}
  • 调用了同步器的lock方法
 //非公平的锁的实现
final void lock() {
    
    
    if (compareAndSetState(0, 1))
       setExclusiveOwnerThread(Thread.currentThread());
    else
       acquire(1);
}
//公平锁的实现
final void lock() {
    
    
    acquire(1);
}
public final void acquire(int arg) {
    
    
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}
  • 对应非公平锁,我们比公平锁多了一个if判断,利用CAS操作,来设置当前AQS的State属性的值
    • 因为Sync继承了AQS类,所以调用的是AQS的compareAndSetState方法
    • 0是初始值,表示可以占用锁 大于0则不能占用(为什么是大于0,因为是可重入锁)
  • 故在第一次加锁,无竞争的时候——执行compareAndSetState(0, 1)
image-20230701152249849
  • 当不是第一次加锁,存在竞争的时候,执行acquire(1); acquire有三个流向
  • !tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
    • tryAcquire——尝试加锁
    • addWaiter——加入队列,也就是CLH
    • acquireQueued
    • 这里使用了&& 特性是如果出现false,后面的条件就不会继续执行

tryAcquire

protected boolean tryAcquire(int arg) {
   throw new UnsupportedOperationException();
}
  • tryAcquire在AQS,也就是父类中没有实现,而是依靠子类的实现,也就是同步类Sync中的实现
  • 这里使用了我们的模板方法的设计模式
 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)) {
    
    
              etExclusiveOwnerThread(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;
 }
  • 如果c==0,说明当前锁没有被占用,就尝试加锁,如果这里成功,tryAcquire这里就返回true,取反就是false,也就不会执行addWaiter和acquireQueued方法
    • 这也是非公平的体现,可能阻塞队列中有线程已经阻塞了,但是这时候一个新的正准备进入阻塞队列中的在这执行成功,成功抢占了锁
    • 我们的公平锁在实现时候,这里会有!hasQueuedPredecessors()进行判断,如果前面有其他等待线程,则不会去执行compareAndSetState来抢占锁
  • 进入else if 说明当前有线程占用着锁,如果当前占用锁的线程和当前尝试加锁的线程相同,那么就走重入锁的逻辑,也就是将state+1
  • 如果都不满足,返回false,则会继续执行addWaiter和acquireQueued方法

addWaiter

private Node addWaiter(Node mode) {
    
    
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
    
    
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
    
    
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
}
  • addWaiter(Node.EXCLUSIVE)
    • EXCLUSIVE表示该线程以独占的方式等待锁
  • pred != null——这个判断就是用来判断我们的队列是否初始化 如果等于null,说明为初始化,如果不等于null,说明已经初始化过

当未初始化时,执行enq方法

   private Node enq(final Node node) {
    
    
        for (;;) {
    
    
            Node t = tail;
            if (t == null) {
    
     // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
    
    
                node.prev = t;
                if (compareAndSetTail(t, node)) {
    
    
                    t.next = node;
                    return t;
                }
            }
        }
    }
  • 进入这个方法说明,当前队列是未初始化的,所以在第一次循环的时候,必然进入if语句,也就是对应创建一个哨兵节点,并让头节点和尾节点都指向这个
    • 这个哨兵节点的没有对应线程,状态也是0

每个节点都必须设置前置节点的 ws 状态为 SIGNAL,所以必须要一个前置节点,而这个前置节点,实际上就是当前持有锁的节点。

问题在于有个边界问题:第一个节点怎么办?他是没有前置节点的。那就创建一个假的。

这就是为什么要创建一个虚拟节点的原因。

总结下来就是:每个节点都需要设置前置节点的 ws 状态(这个状态为是为了保证数据一致性),而第一个节点是没有前置节点的,所以需要创建一个虚拟节点。

  • 第二次进入这个循环,肯定走else判断语句,也就是真正的将等待的需要等待的Node节点入队,然后这个循环结束,因为return了
image-20230701161738068

初始化则执行if内语句

if (pred != null) {
    
    
    node.prev = pred;
   if (compareAndSetTail(pred, node)) {
    
    
        pred.next = node;
        return node;
    }
 }
  • 就是正常的将一个节点放入队列的尾部

acquireQueued

 final boolean acquireQueued(final Node node, int arg) {
    
    
        boolean failed = true;
        try {
    
    
            boolean interrupted = false;
            for (;;) {
    
    
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
    
    
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            	 }
        } finally {
    
    
            if (failed)
                cancelAcquire(node);
        }
    }
  • acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
  • acquireQueued 会在一个死循环中不断尝试获得锁,失败后进入 park 阻塞 ,首先通过node.predecessor()获取当前节点的前置节点
    • 如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败
  • 失败进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 head 的 waitStatus 改为 -1,这次返回 false ,因为返回false,后面的parkAndCheckInterrupt不会执行
    • -1表示资源已经准备好了,就等待释放锁
image-20230701165100811
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    
    
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
    
    
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
    
    
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
    
    
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
  • shouldParkAfterFailedAcquire 执行完毕回到 acquireQueued的循环 ,再次 tryAcquire 尝试获取锁,当然这时state 仍为 1,失败
  • 当再次进入 shouldParkAfterFailedAcquire 时,这时因为其前驱 node 的 waitStatus 已经是 -1,这次返回true
  • 进入 parkAndCheckInterrupt, Thread-1 park(灰色表示)
image-20230701165638756
 private final boolean parkAndCheckInterrupt() {
    
    
        LockSupport.park(this);
        return Thread.interrupted();
}
  • 这里执行parkAndCheckInterrupt是利用LockSupport.park(this) 来实现线程真正的阻塞

如果有多个线程竞争锁失败就会变成这个样子

image-20230701165943091

注意

  • 是否需要 unpark 是由当前节点的前驱节点的 waitStatus == Node.SIGNAL 来决定,而不是本节点的waitStatus 决定

unlock( )获取permit

public void unlock() {
    
    
   sync.release(1);
}
 public final boolean release(int arg) {
    
    
        if (tryRelease(arg)) {
    
    
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
}
protected boolean tryRelease(int arg) {
    
    
   throw new UnsupportedOperationException();
}
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来说,如果当前state为1.且执行解锁的线程是当前占用锁的线程,也设置当前占用锁的线程为null,并且设置state为0,返回true
    • 因为可重入锁的缘故,可能这次解锁不一定成功,也就是返回false,但是不管是否成功,state都会-1,但是只有state为0才会设置占用锁的线程为null
  • tryRelease返回true,当前队列不为 null,并且 head 的 waitStatus = -1,进入 unparkSuccessor 流程
private void unparkSuccessor(Node node) {
    
    
        int ws = node.waitStatus; //获得哨兵节点的状态
        if (ws < 0)
            //将哨兵节点的状态设为0
            compareAndSetWaitStatus(node, ws, 0);
        Node s = node.next;//获得哨兵节点的下一个线程
        if (s == null || s.waitStatus > 0) {
    
    
            //如果这个节点为null,或者状态码大于0,也就是1,说明线程请求锁的请求取消了,就需要将对应的节点移除
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            //如果则唤醒这个线程
        	LockSupport.unpark(s.thread);
    }

为什么非公平

unparkSuccessor我们去回顾acquireQueued 流程

image-20230701173045196如果加锁成功(没有竞争),会

  • 设置exclusiveOwnerThread 为 Thread-1,state = 1
  • head 指向刚刚 Thread-1 所在的 Node,该 Node 清空 Thread
  • 原本的 head 因为从链表断开,而可被垃圾回收

如果这时候有其它线程来竞争(非公平的体现),例如这时有 Thread-4 来了

image-20230701173329039
  • 如果不巧又被 Thread-4 占了先
  • Thread-4 被设置为 exclusiveOwnerThread,state = 1
  • Thread-1 再次进入 acquireQueued 流程,获取锁失败,重新进入 park 阻塞

加锁源码汇总

// Sync 继承自 AQS
static final class NonfairSync extends Sync {
    
    
	 private static final long serialVersionUID = 7316153563782823691L;
    final void lock() {
    
    
		// 首先用 cas 尝试(仅尝试一次)将 state 从 0 改为 1, 如果成功表示获得了独占锁
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
			// 如果尝试失败,进入 ㈠
            acquire(1);
    }
    // ㈠ AQS 继承过来的方法, 方便阅读, 放在此处
    public final void acquire(int arg) {
    
    
			// ㈡ tryAcquire
        if (!tryAcquire(arg) &&// 当 tryAcquire 返回为 false 时, 先调用 addWaiter ㈣, 接着 acquireQueued ㈤
           acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
        ) {
    
    
            selfInterrupt();
        }
    }
    // ㈡ 进入 ㈢
    protected final boolean tryAcquire(int acquires) {
    
    
        return nonfairTryAcquire(acquires);
    }
    // ㈢ Sync 继承过来的方法, 方便阅读, 放在此处
    final boolean nonfairTryAcquire(int acquires) {
    
    
        final Thread current = Thread.currentThread();
        int c = getState();
		  // 如果还没有获得锁
        if (c == 0) {
    
    
				// 尝试用 cas 获得, 这里体现了非公平性: 不去检查 AQS 队列
            if (compareAndSetState(0, acquires)) {
    
    
                setExclusiveOwnerThread(current);
                return true;
            }
        }
		 // 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入
        else if (current == getExclusiveOwnerThread()) {
    
    
				// state++
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
		// 获取失败, 回到调用处
        return false;
    }
    // ㈣ AQS 继承过来的方法, 方便阅读, 放在此处
    private Node addWaiter(Node mode) {
    
    
       // 将当前线程关联到一个 Node 对象上, 模式为独占模式
    	Node node = new Node(Thread.currentThread(), mode);
    	Node pred = tail;
       // 如果 tail 不为 null, cas 尝试将 Node 对象加入 AQS 队列尾部
		if (pred != null) {
    
    
        	node.prev = pred;
        	if (compareAndSetTail(pred, node)) {
    
    
				// 双向链表
        		pred.next = node;
        		return node;
       		 }
       }
		// 尝试将 Node 加入 AQS, 进入 ㈥
        enq(node);
        return node;
     }
	// ㈥ AQS 继承过来的方法, 方便阅读, 放在此处
		private Node enq(final Node node) {
    
    
        for (;;) {
    
    
     	 	Node t = tail;
        	if (t == null) {
    
    
				// 还没有, 设置 head 为哨兵节点(不对应线程,状态为 0)
        		if (compareAndSetHead(new Node())) {
    
    
        			tail = head;
        		}
        	} else {
    
    
				// cas 尝试将 Node 对象加入 AQS 队列尾部
        		node.prev = t;
        		if (compareAndSetTail(t, node)) {
    
    
        			t.next = node;
        			return t;
       		 	}
        	}
        }
      }
	// ㈤ AQS 继承过来的方法, 方便阅读, 放在此处
	final boolean acquireQueued(final Node node, int arg) {
    
    
        boolean failed = true;
        try {
    
    
        	boolean interrupted = false;
        	for (;;) {
    
    
				final Node p = node.predecessor();
				// 上一个节点是 head, 表示轮到自己(当前线程对应的 node)了, 尝试获取
        	  if (p == head && tryAcquire(arg)) {
    
    
					// 获取成功, 设置自己(当前线程对应的 node)为 head
        			setHead(node);
					// 上一个节点 help GC
        			p.next = null;
        			failed = false;
					// 返回中断标记 false
        			return interrupted;
             if (shouldParkAfterFailedAcquire(p, node)// 判断是否应当 park, 进入 ㈦
        		 &&parkAndCheckInterrupt()// park 等待, 此时 Node 的状态被置为 Node.SIGNAL ㈧) 
              {
    
    
        			interrupted = true;
        		}
        	}
        } finally {
    
    
        	if (failed)
        		cancelAcquire(node);
       		}
        }
		// ㈦ AQS 继承过来的方法, 方便阅读, 放在此处
		private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    
    
		  // 获取上一个节点的状态
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL) {
    
    
			// 上一个节点都在阻塞, 那么自己也阻塞好了
        	return true;
         }
		  // > 0 表示取消状态
        if (ws > 0) {
    
    
			// 上一个节点取消, 那么重构删除前面所有取消的节点, 返回到外层循环重试
        	do {
    
    
        		node.prev = pred = pred.prev;
       		} while (pred.waitStatus > 0);
        		pred.next = node;
       	 } else {
    
    
			// 这次还没有阻塞
			// 但下次如果重试不成功, 则需要阻塞,这时需要设置上一个节点状态为 Node.SIGNAL
        	compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
       }
		// ㈧ 阻塞当前线程
		private final boolean parkAndCheckInterrupt() {
    
    
        	LockSupport.park(this);
        	return Thread.interrupted();
        }
}

解锁源码

// Sync 继承自 AQS
static final class NonfairSync extends Sync {
    
    
    // 解锁实现
    public void unlock() {
    
    
        sync.release(1);
     }
    // AQS 继承过来的方法, 方便阅读, 放在此处
    public final boolean release(int arg) {
    
    
			// 尝试释放锁, 进入 ㈠
        if (tryRelease(arg)) {
    
    
			 // 队列头节点 unpark
            Node h = head;
            if (h != null// 队列不为 null
               h.waitStatus != 0 && // waitStatus == Node.SIGNAL 才需要 unpark
             ) {
    
    
					// unpark AQS 中等待的线程, 进入 ㈡
                unparkSuccessor(h);
            }
            return true;
         }
        return false;
     }
    // ㈠ Sync 继承过来的方法, 方便阅读, 放在此处
    protected final boolean tryRelease(int releases) {
    
    
		 // state--
        int c = getState() - releases;
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
		  // 支持锁重入, 只有 state 减为 0, 才释放成功
        if (c == 0) {
    
    
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }
    // ㈡ AQS 继承过来的方法, 方便阅读, 放在此处
    private void unparkSuccessor(Node node) {
    
    
		// 如果状态为 Node.SIGNAL 尝试重置状态为 0
		// 不成功也可以
       int ws = node.waitStatus;
       if (ws < 0) {
    
    
           compareAndSetWaitStatus(node, ws, 0);
        }
		// 找到需要 unpark 的节点, 但本节点从 AQS 队列中脱离, 是由唤醒节点完成的
    	Node s = node.next;
		// 不考虑已取消的节点, 从 AQS 队列从后至前找到队列最前面需要 unpark 的节点
		if (s == null || s.waitStatus > 0) {
    
    
        	s = null;
        	for (Node t = tail; t != null && t != node; t = t.prev)
        		if (t.waitStatus <= 0)
        		s = t;
        	}
        	if (s != null)
        	LockSupport.unpark(s.thread);
        }
 }

读写锁

多线程之间,并发读取数据不会有线程安全问题,只有再更新数据(增删改)时会有线程安全问题。如果两种场景下都用同一个锁,就会产生极大的性能损耗。所以读写锁因此而产生。

  • 读写锁(readers-writer lock),看英文可以顾名思义,在执行加锁操作时需要额外表明读写意图,复数读者之间并不互斥而写者则要求与任何人互斥

  • 多个线程并发访问读锁(读数据),则多个线程都能访问到读锁,读锁和读锁是并发的,不互斥

  • 两个线程都需要访问写锁,则这两个线程互斥,只有一个线程能成功获得锁,其他线程阻塞

  • 当一个线程读,一个线程写(也互斥,只有写线程结束,读线程才能继续)

ReentrantReadWriteLock

读写锁就是把读操作和写操作区分对待. Java 标准库提供了 ReentrantReadWriteLock 类, 实现了读写锁.synchronized不是读写锁

  • ReentrantReadWriteLock.ReadLock 类表示一个读锁. 这个对象提供了 lock / unlock 方法进行 加锁解锁.
  • ReentrantReadWriteLock.WriteLock 类表示一个写锁. 这个对象也提供了 lock / unlock 方法进 行加锁解锁.
class DataContainer{
    
    
    private Object date;
    private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
    private ReentrantReadWriteLock.ReadLock r = rw.readLock();
    private ReentrantReadWriteLock.WriteLock w = rw.writeLock();
    public Object read(){
    
    
        System.out.println("获取读锁");
        r.lock();
        try {
    
    
            System.out.println("读取");
            TimeUnit.SECONDS.sleep(1);
            return date;
        }catch (InterruptedException e) {
    
    
            e.printStackTrace();
            return null;
        }finally {
    
    
            System.out.println("释放读锁");
            r.unlock();
        }
    }
    public Object write(){
    
    
        System.out.println("获取写锁");
        w.lock();
        try {
    
    
            System.out.println("写入");
            TimeUnit.SECONDS.sleep(1);
            return date;
        }catch (InterruptedException e) {
    
    
            e.printStackTrace();
            return null;
        }finally {
    
    
            System.out.println("释放写锁");
            w.unlock();
        }
    }
}
public class ReentrantReadWriteLockDemo {
    
    

    public static void main(String[] args) {
    
    
        DataContainer dataContainer = new DataContainer();
        new Thread(() -> {
    
    
            dataContainer.read();
        }, "t1").start();
        new Thread(() -> {
    
    
            dataContainer.read();
        }, "t2").start();
    }
}
获取读锁
获取读锁
读取
读取
释放读锁
释放读锁
  • 读锁之间不互斥
public class ReentrantReadWriteLockDemo {
    
    

    public static void main(String[] args) {
    
    
        DataContainer dataContainer = new DataContainer();
        new Thread(() -> {
    
    
            dataContainer.write();
        }, "t1").start();
        new Thread(() -> {
    
    
            dataContainer.read();
        }, "t2").start();
    }
}
获取写锁
获取读锁
写入
释放写锁
读取
释放读锁
  • 读写锁之间互斥
  • 写写之间互斥

注意事项

  • 读锁不支持条件变量
  • 重入时升级不支持:即持有读锁的情况下去获取写锁,会导致获取写锁永久等待
  • 重入时降级支持:即持有写锁的情况下去获取读锁
r.lock();
try {
    
    
	// ...
	w.lock();
	try {
    
    
		// ...
	} finally{
    
    
		w.unlock();
	}
} finally{
    
    
	r.unlock();
}

猜你喜欢

转载自blog.csdn.net/qq_50985215/article/details/131511134