并发编程(七)

1.管程    Java同步的设计思想

管程:指的是管理共享变量以及对共享变量的操作过程,让他们支持并发;

互斥:同一时刻,只允许一个线程访问资源;

同步:线程之间的通信与协作

2.MESA 模型:

MESA 模型(MESA-Model)是一个用于描述计算机系统中并发编程的模型,Java中现在的并发库中很多的类,都是依据这个模型进行设计的

 上图就是MESA 的模型,并发中主要存在的问题就是上述所说的互斥和共享功能,这个模型中的入口处有个等待队列就是为了解决互斥功能的,里面的条件等待队列就是解决共享功能的

3.AQS
什么是AQS ?

AQS 是 Java 中的一个重要的同步器框架,"AbstractQueuedSynchronizer" 的缩写,AQS 提供了一种用于实现各种同步器的基础框架,例如锁(Lock)、信号量(Semaphore)、倒计数器(CountDownLatch)等等

如何通过AQS实现一把自己的独占锁?

代码:

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class HuangLock extends AbstractQueuedSynchronizer {
    @Override
    protected boolean tryAcquire(int arg) {
        if (compareAndSetState(0, 1)) {
            setExclusiveOwnerThread(Thread.currentThread());//设置当前正在用的线程
            return true;
        }
        return false;
    }

    @Override
    protected boolean tryRelease(int arg) {
        setExclusiveOwnerThread(null);//将当前正在使用的线程设为空
        setState(0);
        return true;
    }

    public void lock() { acquire(1);}

    public boolean tryLock() { return tryAcquire(1);}

    public void unlock() { release(1);}

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

}

其实很简单就是继承之后,新增加锁 解锁 , 等常用方法,这里解释下两个override 的方法,这两个方法AQS中都没有实现,需要我们自己实现,即通过CAS的方法针对 state 的值进行获取 和 对state 值的替换。

4.源码分析

其实源码可以通过几个需要知道的知识点去看,比如如何实现互斥,即独占锁,还有公平与非公平

等待中的线程如何进入等待队列的,等待队列的结构是什么,以及线程释放锁之后的出队以及对下

一个线程的唤醒,这都是我们需要去了解的;

 在IDEA 中,右击选择Thread 我们就能在多线程中进行debug ,能帮助我们更好的去debug阅读源

码。

下面我们通过 ReentrantLock 进行源码的解读:

 

 我们发现Java中这些同步类基本的套路都是,通过一个Sync 来继承AQS 然后通过这个内部类将AQS的所有调用都映射到 Sync 方法;我个人的思路是从lock()方法入手去阅读源码,我们假设是第一个线程进入lock()方法:

 

 图一是非公平锁的lock()方法,图二是公平锁的lock()方法;显而易见,非公平锁在获取锁资源的时候会进行一个CAS 操作,来判段我们是否能获取锁,如果获取不到再排队,acquire() 中的参数1 大家可能不理解,其实就是获取几把锁,用于可重入锁的次数累加,到时候释放锁的时候把这个参数减到0就是全部释放了,后面如果看到相关源码会说;好我们现在进入acquire () 方法

 这段代码呢,看字面意思就是获取不到,这个线程就挂起,现成我们来看看,tryAcquire()方法

 这个是非公平的,公平的和它公用这一个,我们发现这个state 变量,就是判断我们能不能获取锁的关键,如果 state =  0 那么就能够获取到锁,如果 state != 0 ,那就代表获取不到;这里就能看到上面参数arg的用处了,int nextc = c + acquire ;就是为了实现可重入;

如果我们是第一个线程那么肯定是可以获取到锁的 所以我们的第一个线程很简单,就这个轻而易举的获取到了锁,然后设置了当前锁是由哪个线程独占的; setExclusiveOwnerThread(Thread.currentThread());

接下来我们是第二个线程再次进入lock() 这个方法;

我们会发现我们获取不到锁,并且会执行

acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 这个方法我们进入看看:

我们先看看addWaiter() 方法 这里的入参是个空的node 节点;我们来看下这个node 是什么样的

 通过它的pre 和 next 属性,我们不难发现,由多个 Node 构成的是一种双向链表的队列结构;我们继续往下看,当第二个线程进来后,它的 tail 节点肯定是 NULL ,因为这个队列还没创建,所以它会直接走enq() 方法;

 因为线程二需要入队,所以线程二在到这一步的时候,需要创建一个队列并进行入队操作,我们看到源码是利用for 循环来进行这种操作的;当我们的尾结点为空时,它会通过CAS 将一个空节点设置为我们的head 节点,由于是双向链表且只有一个节点,所以这个节点既是头节点也是也是尾节点;当队列创建成功后,就会把线程二这个node 的前驱节点设置为上一个的尾结点,然后通过CAS 将现在的节点设置为新的尾结点,然后再把上次一的节点指向我们现在的尾节点,我们画个图就清楚了如下图:

接下来我们跳出来进入acquireQueued()方法:

 这个方法主要是用来挂起线程的,首先它会获取当前节点的前面一个节点P ,判断是不是头节点,如果是头节点那么它会尝试去获取锁,因为中断需要上下文切换比较消耗性能,然后它会判断是不是该把线程二挂起,挂起前它会把上个节点的waitStatus 改成 -1 ,这个意思是代表它可以唤醒下个节点的线程;

 

 当准备工作做好,它就会挂起线程二

线程三入队的情况和二一样,如下图:

 

 上面这些就l是ock()方法所涉及的相关源码;接下来我们看下 unlock() 的相源码:

 

 

 通过上面的截图可知主要就是把state 设置为0 , 再把当前独占锁的线程置空; int c = getState() - releases ; 就是之前说的可重入会对 state 进行累加;然后回到release() 调用 unparkSuccessor(h)

 

 这里直接把线程二给唤醒了,

 如上图所示,线程二现在去获取锁,可以成功获取,通过setHead 方法和 p.next = null ; 就把第一个节点给断开了,线程一就出去了 

 线程一出队之后如下图所示:线程二变成了头节点

 

 以上就是AQS相关源码的分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值