并发-Java中的锁---Lock接口、队列同步器AQS笔记

Lock接口

java se5之后并发包中增加了Lock接口实现锁的功能,提供了与synchronized关键字类似的功能,但需要显式地获取和释放锁。

不要将获取锁的过程写到try块中,如果在获取锁时发生异常,异常抛出的同时也会导致锁的无故释放。

Lock提供synchronized关键字不具备的主要特性

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

API

  • lock():获取锁
  • void lockInterruptibly() throws InterruptedException:可中断的获取锁,在锁的获取中可以中断当前线程
  • boolean tryLock():尝试非阻塞获取锁,调用该方法会立刻返回,如果能够获取则返回true,否则返回false
  • boolean tryLock(long time, TimeUnit unit)throws InterruptedException:超时的获取锁,一下三种情况会返回
    • 当前线程在超时时间内获得了锁
    • 当前线程在超时时间内被中断
    • 超时时间结束,返回false
  • void unlock():释放锁
    • Condition newCondition():获取等待通知组件,该组件和当前的锁绑定,当前线程只有获得了锁,才能调用该组件的wait方法,调用后,当前线程将释放锁。

队列同步器AQS

用来构建锁或者其他同步组件的基础框架,使用了一个int成员变量表示同步状态,通过内置FIFO队列来完成资源获取线程的排队工作。

使用:

子类继承同步器并实现他的抽象方法来管理同步状态,使用同步器提供的方法(getState(),setState(int newState),compareAndSetState(int expect,int update))操作能够保证状态的改变是安全的。

同步器仅仅定义了若干同步状态获取和释放的方法来供自定义同步组件使用,同步器既可以支持独占式地获取同步状态,也可以支持共享式地获取同步状态。

  • 锁是面向使用者的,定义了使用者与锁交互的接口,隐藏了实现细节
  • 同步器面向的是锁的实现者,简化了锁的实现方式,屏蔽了同步状态管理,线程的排队,等待与唤醒等底层操作
队列同步器的接口与示例

使用者继承同步器并重写指定的方法,随后将同步器组合在中自定义同步组件的实现中,并调用同步器提供的模板方法,这些模板方法将调用使用者重写的方法。

访问或修改同步状态

  • getState():获取当前同步状态
  • setState(int newState):设置当前同步状态
  • compareAndSetState(int expect, int update):使用cas设置当前状态,该方法能够保证状态设置为原子性

同步器可重写方法

  • protected boolean tryAcquire(int arg):独占式获取同步状态,实现该方法需要查询当前状态并判断同步状态是否符合预期,然后进行cas设置同步状态
  • protected boolean tryRelease(int arg):独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态
  • protected int tryAcquireShared(int arg);共享式获取同步状态,返回大于等于0的值,表示获取成功,反之,获取失败
  • protected boolean tryReleaseShared(int arg):共享式释放同步状态
  • protected boolean isHeldExclusively():当前同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程锁独占

同步器模板方法

  • void acquire(int arg):独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则,将会进入同步队列等待,该方法将会调用重写的tryAcquire(int arg)
  • void acquireInterruptibly(int arg):同上,但该方法响应中断,当前线程未获取到同步状态而进入同步队列,如果当前线程被中断,则该方法会抛出InterruptedException并返回。
  • boolean tryAcquireNanos(int arg.long nanos):在acquireInterruptibly基础上增加了超时限制,如果当前线程在超时时间内没有获取到同步状态,那么会返回false,如果获取到了返回true
  • void acquireShared(int arg): 共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占锁的主要区别是同一时刻可以有多个线程获取到同步状态
  • void acquireSharedInterruptibly(int arg):与acquireShared相同,该方法响应中断
  • boolean tryAcquireShareNanos(int arg,long nanos):在上面的基础上增加了超时限制
  • boolean release(int arg):独占式的释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒
  • boolean releaseShared(int arg):共享式的释放同步状态
  • Collection getQueuedThreads():获取等待在同步队列上的线程集合

模板分为3类:

  • 独占式获取与释放同步状态
  • 共享式获取与释放同步状态
  • 查询同步队列中的等待线程情况

独占锁:Mutex.java

  • 自定义内部组件,同一时刻只允许一个线程占有锁
  • 静态内部类:继承了同步器并实现了独占式获取和释放同步状态。
  • tryAcquire(int acquires) 方法中,经过cas设置成功(同步状态设置为1),代表获取同步状态
  • tryRelease(int realeases)方法中,只是将同步状态重置为0,
package com.dahua.tool;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @author: 308158
 * @description: TODO
 * @date: Created in 2023-8-31 15:40
 */
public class Mutex implements Lock {

    //静态内部类,自定义同步器
    private static class Sync extends AbstractQueuedSynchronizer {
        //是否处于占用状态
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        //当状态为0 的时候获取锁
        @Override
        public boolean tryAcquire(int acquires) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        //释放锁,状态设置为0
        @Override
        protected boolean tryRelease(int releases) {
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        //返回一个condition 每个condition都包含一个condition队列
        Condition newCondition() {
            return new ConditionObject();
        }
    }

    private final Sync sync = new Sync();


    public boolean isLocked() {
        return sync.isHeldExclusively();
    }

    public boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }


    @Override
    public void lock() {
        sync.acquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override
    public void unlock() {
        sync.release(1);
    }

    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }
}
队列同步器的实现分析
  • 同步队列

    • 一个FIFO双向队列
    • 节点NODE:用来保存获取同步状态失败的线程引用,等待状态以及前驱和后继节点。当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为一个节点并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。
      • int waitStatus:等待状态
        • cancelled:值为1,在同步队列中等待的线程等待超时或者被中断,需从同步队列中取消等待
        • signal:值为-1,后继节点线程处于等待状态,当前节点的线程如果释放了同步状态或被取消,将会通知后继节点,使后继节点的线程得以运行
        • condition:值为-2,节点在等待队列,节点线程等待在condition上,当其他线程对condition调用了signal方法后,该节点将会从等待队列中转移到同步队列中,加入到对同步状态的获取中
        • PROPAGATE:值为3,表示下一次共享式同步状态获取将会无条件被传播下去
        • INITIAL,值为0,初始状态
      • Node prev:前驱节点,当节点加入同步队列时被设置(尾部添加)
      • Node next:后继节点
      • Node nextWaiter:等待队列中的后继节点,如果当时节点是共享的,那么这个字段将是一个SHARED常量,即节点类型(独占和共享)和等待队列中的后继节点共用同一个字段
      • Thread thread 获取同步状态的线程
    • 节点构成同步队列,同步器拥有首节点和尾节点,没有成功获取同步状态的线程将会成为节点加入该队列的尾部。
    • 当线程无法获取到同步状态被构造成为节点并加入到同步队列中,加入过程中要保证线程安全,同步器提供了基于CAS的设置尾节点的方法,设置成功后,当前节点才正式与之前的尾节点建立关联。
    • 设置首节点通过获取同步状态成功的线程来完成的,由于只有一个线程能够成功获取到同步状态,因此设置头节点的方法并不需要使用cas来保证,只需要将首节点设置成为原首节点的后继节点并断开原首节点的next引用即可
  • 独占式同步状态获取与释放

    • acquire:对中断不敏感。
    • 主要逻辑:调用自定义同步器实现的tryAcquire方法(保证线程安全的获取同步状态)
      • 获取失败,则构造同步节点(独占式Node.EXCLUSIVE,同一时刻只能有一个线程成功获取同步状态)并通过addWaiter方法将该节点加入到同步队列的队尾
    • 调用acquireQueued方法,使该节点以死循环的方式获取同步状态
    • 如果获取不到则阻塞节点中的线程,而被在阻塞线程的唤醒主要依靠前驱节点的出队或阻塞线程被中断来实现
    • 节点进入同步队列之后,进入一个自旋的过程,每个节点都在自省地观察,当条件满足,获取到了同步状态,就可以从自旋过程中退出,否则依旧留在这个自旋过程中(并阻塞节点的线程)
    • acquireQueued(final Node node, int arg)方法中,当线程在死循环中尝试获取同步状态,而只有前驱节点是头节点才能够尝试获取同步状态
      • 头节点是成功获取到同步状态的节点,而头节点的线程是否了同步状态之后,将会唤醒其后继节点,后继节点的线程被唤醒后需要检查做自己的前驱节点是否是头节点
      • 维护同步队列的FIFO原则,该节点中,节点自旋获取同步状态
  • 共享式同步状态获取与释放

    • 与独占式区别:同一时刻能否有多个线程同时获取到同步状态。
    • 通过调用同步器的acquireShare(int arg)方法可以共享地获取同步状态
      • 同步器调用tryAcquireShared(int arg)方法尝试获取同步状态
        • 方法返回值为int类型,返回值大于等于0时表示能够获取到同步状态。
        • 方法在自旋过程中,如果当前节点的前驱为头节点时,尝试获取同步状态,如果返回值大于等于0,表示该次获取同步状态成功并从自旋过程中退出。
    • 释放同步状态,releaseShared,释放后,将会唤醒后续处于等待状态的节点、
    • 对于支持多个线程同时访问的并发组件(例如,semaphore)和独占式区别在于tryReleaseShared方法必须确保同步状态(或资源数)线程安全释放,一般通过循环cas保证。
  • 独占式超时获取同步状态

    • 调用同步器doAcquireNanos方法可以超时获取同步状态,指定时间内获取同步状态,获取到为true,否则false
    • 同步器提供的acquireInterruptibly方法等待获取同步状态时,如果当前线程被中断,会立刻返回,抛出InterruptedException
    • doAcquireNanos方法在支持响应中断基础上增加了超时获取的特性,针对超时获取,主要需要计算出需要睡眠的时间间隔
    • 自旋过程中,当节点的前驱节点为头节点时尝试获取,
      • 成功返回
      • 当前线程获取同步状态失败,判断是否超时
        • 没有超时,重新计算超时间隔nanosTimeout,然后使当前线程等待nanosTimeout纳秒
        • 如果nanosTimeout小于等于spinForTimeoutThreshold时,将不会使该进程进行超时等待,而是进入快速的自旋过程
  • 自定义同步组件-TwinsLock

    • 同步工具:该工具在同一时刻,只允许至多两个线程同时访问,超过两个线程的访问将被阻塞,将这个工具命名为TwinsLock

    • 确定访问模式:能同一时刻支持多个线程访问是共享式访问,需要使用同步器提供的acquireShared方法和shared相关的方法,TwinsLock要重写tryAcquireShared和tryReleaseShared方法,这样保证同步器共享式同步状态的获取与释放方法得以执行

    • 定义资源数:同一时刻允许最多两个线程同时访问,表明同步资源数为2,这样设置初始状态status为2,当线程获取,status减1,线程释放,加1,状态合法范围0(表示当前已经有两个线程获取了同步资源,再有其他获取,阻塞),1,2

    • 组合自定义同步器:

    • import java.util.concurrent.TimeUnit;
      import java.util.concurrent.locks.AbstractQueuedSynchronizer;
      import java.util.concurrent.locks.Condition;
      import java.util.concurrent.locks.Lock;
      
      public class TwinsLock implements Lock {
      
      
         //自定义同步器Sync,同步器会先计算出获取后的同步状态,然后通过cas确保状态的正确设置
          private final Sync sync = new Sync(2);
          private static final class Sync extends AbstractQueuedSynchronizer{
              Sync(int count) {
                  if(count <= 0){
                      throw new IllegalArgumentException("count must large than zero");
                  }
                  setState(count);
              }
      
              public int tryAcquireShared(int reduceCount){
                  for (;;){
                      int current = getState();
                      int newCount = current - reduceCount;
                      if(newCount < 0 || compareAndSetState(current, newCount)){
                          return newCount;
                      }
                  }
              }
              public boolean tryReleaseShared(int returnCount){
                  for (;;){
                      int current = getState();
                      int newCount = current + returnCount;
                      if(compareAndSetState(current, newCount)){
                          return true;
                      }
                  }
              }
          }
      
        @Override
          public void lock() {
              sync.acquireShared(1);
          }
      
          @Override
          public void lockInterruptibly() throws InterruptedException {
      
          }
      
          @Override
          public boolean tryLock() {
              return false;
          }
      
          @Override
          public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
              return false;
          }
      
          @Override
          public void unlock() {
              sync.releaseShared(1);
          }
      
          @Override
          public Condition newCondition() {
              return null;
          }
          
          public static void main(String[] args) {
              final Lock lock = new TwinsLock();
              class Worker extends Thread{
                  @Override
                  public void run() {
                      while (true){
                          lock.lock();
                          try{
                              Thread.sleep(1);
                              System.out.println(Thread.currentThread().getName());
                              Thread.sleep(1);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          } finally {
                              lock.unlock();
                          }
                      }
                  }
              }
              for (int i = 0; i < 10; i++) {
                  Worker w = new Worker();
                  w.setDaemon(true);
                  w.start();
              }
              for (int i = 0; i < 10; i++) {
                  try {
                      Thread.sleep(1);
                  } catch (InterruptedException e) {
      
                  }
                  System.out.println();
              }
          }
      }
      

参考:Java并发编程的艺术

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值