JAVA劝退系列AQS源码阅读之二

前言

继续接上一篇的看AQS源码

lock可重入特性

tryAcquire()锁竞争逻辑

在这里插入图片描述
上图代码中,当state不为0时,判断获取锁的线程是否为当前线程
current == getExclusiveOwnerThread(),是的话信号量加1int nextc = c + acquires;
在这里插入图片描述
如果是,则state进行加法,并修改state值,讲一下什么是可重入锁,

  public static void reentrantLock(){
        String threadName = Thread.currentThread().getName();
        lock.lock();
        reen2();
        lock.unlock();
  }
  public static void reen2(){
     lock.lock();
     //业务逻辑
     lock.unlock();
  }

上图所示的就是可重入特性,再看个例子

public class Juc02_Thread_ReentrantLock {

    private static ReentrantLock lock = new ReentrantLock(true);

    public static void reentrantLock(){
        String threadName = Thread.currentThread().getName();
        //默认创建的是独占锁,排它锁;同一时刻读或者写只允许一个线程获取锁
        lock.lock();
        log.info("Thread:{},第一次加锁",threadName);
            lock.lock();
            log.info("Thread:{},第二次加锁",threadName);
            lock.unlock();
            log.info("Thread:{},第一次解锁",threadName);
        lock.unlock();
        log.info("Thread:{},第二次解锁",threadName);
    }

    public static void main(String[] args) {
        Thread t0 = new Thread(new Runnable() {
            @Override
            public void run() {
                reentrantLock();
            }
        },"t0");

        t0.start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                reentrantLock();
            }
        },"t1");

        t1.start();
    }

}

执行结果
在这里插入图片描述
可重入的特性就是加了几次锁,必须释放几次锁。

addWaiter入队介绍

tryAcquire如果为false,就是尝试获取锁失败。就进入到了这个方法acquireQueued

public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
}

在这里插入图片描述
这个性质有俩种,独占(也可叫互斥)和共享,

 static final Node SHARED = new Node();//共享
 static final Node EXCLUSIVE = null;//独占

除了这俩个还有Node类还有几个重要的变量

	volatile Node prev;//前驱指针
	volatile Node next;//后继指针
	volatile Thread thread; //线程的引用,线程这个对象是需要被保存起来的,以便后续唤醒
	volatile int waitStatus;

node除了独占和共享还有,waitStatus节点的生命状态(也叫信号量):
默认0(init),还有

  /** 代表出现异常,中断引起的,可被废弃  */
  static final int CANCELLED =  1;
  /** 可被唤醒 */
  static final int SIGNAL    = -1;
  /** 条件等待 */
  static final int CONDITION = -2;
  /**
   *广播
   */
  static final int PROPAGATE = -3;

类似于mysql中某个字段的不同状态,存储不同状态的数据。今天先介绍3种,SIGNAL,CANCELLED
init
再来看这行代码。

 Node node = new Node(Thread.currentThread(), mode);

这个结点被创建来之后就是这样,waitStatus为int类型,默认肯定是0了,我们加锁t0加锁,t1排队
在这里插入图片描述
当aqs刚new出来的时候,head和tail都是null
在这里插入图片描述
在这里插入图片描述
那么就会走enq(node)方法。

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;
             }
         }
     }
 }
队列在第一次使用的时候初始化,如下图

在这里插入图片描述
t1线程入队,t1前面的 Node t 就是初始化的空结点
在这里插入图片描述
最后t.next=node形成双向链表。在这里插入图片描述

acquireQueued

当前节点,线程要开始阻塞

在这里插入图片描述
是不是一个线程入队成功,要马上进行休眠?
答案是否定的,

 节点阻塞之前还得再尝试一次获取锁:
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                //是第一个节点,还要再去抢一次,尽可能不让线程阻塞,内核态用户态切换很浪费cpu
                //1,能够获取到,节点出队,
                if (p == head && tryAcquire(arg)) {//如果前面结点是头部结点,并且能获取锁成功
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 2、不能获取到,阻塞等待被唤醒
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

上面代码执行完相当于,
1,能够获取到,节点出队,并且把head往后挪一个节点,新的头结点就是当前节点,对比上面的图
在这里插入图片描述

在这里插入图片描述

这也就是并发队列的设计

2、不能获取到,阻塞等待被唤醒

if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
     interrupted = true;//唤醒线程

看下shouldParkAfterFailedAcquire方法

private static boolean shouldParkAfterFailedAcquire(Node pred,
                                                    Node node) {
         //pred是前驱节点,node是当前节点                                             
        int ws = pred.waitStatus;//前驱节点的状态
        if (ws == Node.SIGNAL)//1是可唤醒的
            return true;//可以被唤醒的
        if (ws > 0) { //代表着节点被废弃掉了
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else { //为0则cas把前驱节点waitStatus改为1
           reAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

通过首节点的状态waitStatus是否为-1,判断当前节点能否被唤醒
在这里插入图片描述
总结下:

 1第一轮循环,修改head状态,修改signal=-1,标记出可以被唤醒。
 2.第2轮循环,执行ws == Node.SIGNAL,return true,阻塞线程,并且需要判断线程是否是有中断信号唤醒的!

在这里插入图片描述

  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 final boolean tryRelease(int releases) {
      int c = getState() - releases;//1-1为0,锁释放
      if (Thread.currentThread() != getExclusiveOwnerThread())
          throw new IllegalMonitorStateException();
      boolean free = false;
      if (c == 0) {
          free = true;
          setExclusiveOwnerThread(null);
      }
      setState(c);
      return free;
  }

selfInterrupt()

 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
 }

当尝试去获取锁失败即tryAcquire为false,以及acquireQueued为true的话,它会执行
selfInterrupt方法

 static void selfInterrupt() {
        Thread.currentThread().interrupt();
 }

为什么要进行线程的自我中断呢

注意在acquireQueued的方法体里
在这里插入图片描述

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        //Thread.interrupted()获取了线程的中断信号,并清掉了线程的中断信号
        return Thread.interrupted();
}

所以需要在acquire方法那里进行一次自我标记,那么为什么这么做呢?

中断是什么?
在很早的时候,直接进行stop0方法,强制把线程杀死。存在各种各样问题。
后来就出现了interrupt方法

lockInterruptibly()

看个例子

@Slf4j
public class Juc05_Thread_Interrupt {

    private static ReentrantLock lock = new ReentrantLock(true);

    public static void reentrantLock() throws InterruptedException {
        String threadName = Thread.currentThread().getName();
        boolean flag = false;
        lock.lockInterruptibly();
            log.info("Thread:{},加锁成功!",threadName);
            while(true){
                if(Thread.interrupted()){
                    break;
                }
                //逻辑,批处理数据
                //逻辑
            }
        lock.unlock();
        log.info("Thread:{},锁退出同步块",threadName);
    }

    public static void main(String[] args) {
        Thread t0 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    reentrantLock();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"t0");
        t0.start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    reentrantLock();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    //异常处理
                }
            }
        },"t1");
        t1.start();

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        t1.interrupt();

    }

}

运行结果:
在这里插入图片描述
lockInterruptibly()方法的意思就是,竞争锁失败,如果产生中断会报异常,而lock方法不会报异常。
源码:

private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    //这里和之前不同会抛异常
                    //跳出for循环执行finally,failed本身为true,cancelAcquire方法把node节点变为
                    // node.waitStatus = Node.CANCELLED;1为取消状态
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值