JUC源码分析:通过ReentrantLock阅读AbstractQueuedSynchronizer源码

一、概述

ReentrantLock进行上锁的流程如下图所示,我们将按照下面的流程分析ReentrantLock上锁的流程,在此过程中阅读AbstractQueuedSynchronizer源码。

 AQS 的数据结构如下图所示。 AQS大家还记得吗?最核心的是它的一个共享的int类型值叫做state,这个state用来干什么,其实主要是看他的子类是怎么实现的,比如ReentrantLock这个state是用来干什么的?拿这个state来记录这个线程到底重入了多少次,比如说有一个线程拿到state这个把锁了,state的值就从0变成了1,这个线程又重入了一次,state就变成2了,又重入一次就变成3等等,什么时候释放了呢?从3变成2变成1变成0就释放了,这个就是AQS核心的东西,一个数,这个数代表了什么要看子类怎么去实现它,那么在这个state核心上还会有一堆的线程节点,当然这个节点是node,每个node里面包含一个线程,我们称为线程节点,这么多的线程节点去争用这个state,谁拿到了state,就表示谁得到了这把锁,AQS得核心就是一个共享的数据,一堆互相抢夺竞争的线程,这个就是AQS。

二、源码阅读

 先进入ReentrantLock.lock方法。

 再进入内部类NonfairSync的lock方法。

 点击acquire方法进入AbstractQueuedSynchronizer.acquire方法。

 进入tryAcquire方法回到ReentrantLock.nonfairTryAcquire方法。

 

 再看另一个条件acquireQueued(addWaiter(Node.EXCLUSIVE)。AbstractQueuedSynchronizer.addWaiter方法将当前线程放入阻塞队列中。

 之后它会从队列中不断请求获取线程,直到获取为止。

三、拓展点

我们再来讲一个细节,我们看addWaiter()这个方法里边有一个node.setPrevRelaved(oldTail),这个方法的意思是把当前节点的前置节点写成tail,进入这个方法你会看到PREV.set(this,p),那这个PREV是什么东西呢?当你真正去读这个代码,读的特别细的时候你会发现,PREV有这么一个东西叫VarHandle,这个VarHandle是什么呢?这个东西实在JDK1.9之后才有的,我们说一下这个VarHandle,Var叫变量(variable),Handle叫句柄,打个比方,比如我们写了一句话叫Object o= new Object(),我们new了一个Object,这个时候内存里有一个小的引用“O”,指向一段大的内存这个内存里是new的那个Object对象,那么这个VarHandle指什么呢?指的是这个“引用”,我们思考一下,如果VarHandle代表“引用”,那么VarHandle所代表的这个值PREV是不是也这个“引用”呢?当然是了。这个时候我们会生出一个疑问,本来已经有一个“O”指向这个Object对象了,为什么还要用另外一个引用也指向这个对象,这是为什么?

//JDK源码
public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {
	public final void acquire(int arg){
        //判断是否得到锁
		if(!tryAcquire(arg) 
           && acquireQueued(addWaiter(Node.EXCLUSIVE),arg))
       	 selfInterrupt();
	}
    
    private Node addWaiter(Node mode){
        //获取当前要加进来的线程的node(节点)
		Node node = new Node(mode);
        for(;;){
            //回想一下AQS数据结构图
		   Node oldTail = tail;
            if(oldTail != null){
                //把我们这个新节点的前置节点设置在等待队列的末端
				node.setPrevRelaved(oldTail);
                //CAS操作,把我们这个新节点设置为tail末端
                 if(compareAndAetTail(oldTail,node)){
					oldTail.next = node;
                      return node;
                 }
            }else{
                initializeSuncQueue();
            }
        }
    }
    
    final void setPrevRelaved(Node p){
        PREV.set(this,p);
    }
    
    private static final VarHandle PREV;
    static {
        try{
			MethodHandles.Lookup l = MethodHandles.lookup():
             PREV = l.findVarHandle(Node.class,"prev",Node.class);
        }catch(ReflectiveOperationException e){
            throw new ExceptionInInitializerError(e);
        }
    }
}

我们来看一个小程序,用这个小程序来理解这个VarHandle是什么意思,在这个类,我们定义了一个int类型的变量x,然后定义了一个VarHandle类型的变量handle,在静态代码块里设置了handle指向T01_HelloVarHandle类里的x变量的引用,换句话说就是通过这个handle也能找到这个x,这么说比较精确,通过这个x能找到这个x,里面装了个8,通过handle也能找到这个x,这样我们就可以通过这个handle来操作这个x的值,我们看main方法里,我们创建了T01_HelloVarHandle对象叫t,这个t对象里有一个x,里面还有个handle,这个handle也指向这个x,既然handle指向x,我当然可以(int)handle.get(t)拿到这个x的值不就是8吗?我还可以通过handle.set(t,9)来设置这个t对象的x值为9,读写操作很容易理解,因为handle指向了这个变量,但是最关键的是通过这个handle可以做什么事呢?handle.compareAndSet(t,9,10),做原子性的修改值,我通过handle.compareAndSet(t,9,10)把9改成10改成100,这是原子性的操作,你通过x=100 ,它会是原子性的吗?当然int类型是原子性的,但是long类型呢?就是说long类型连x=100都不是原子性的,所以通过这个handle可以做一些compareAndSet操作(原子操作),还可以handle.getAndAdd()操作这也是原子操作,比如说你原来写x=x+10,这肯定不是原子操作,因为当你写这句话的时候,你是需要加锁的,要做到线程安全的话是需要加锁的,但是如果通过handle是不需要的,所以这就是为什么会有VarHandle,VarHandle除了可以完成普通属性的原子操作,还可以完成原子性的线程安全的操作,这也是VarHandle的含义。

在JDK1.9之前要操作类里边的成员变量的属性,只能通过反射完成,用反射和用VarHandle的区别在于,VarHandle的效率要高的多,反射每次用之前要检查,VarHandle不需要,VarHandle可以理解为直接操纵二进制码,所以VarHandle反射高的多

//小程序
public class T01_HelloVarHandle {
    int x = 8;
    private static VarHandle handle;
    static {
        try {
            handle = MethodHandles.lookup().findVarHandle(T01_HelloVarHandle.class, "x", int.class);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        T01_HelloVarHandle t = new T01_HelloVarHandle();
        //plain read / write
        System.out.println((int)handle.get(t));
        handle.set(t,9);
        System.out.println(t.x);
        handle.compareAndSet(t, 9, 10);
        System.out.println(t.x);
        handle.getAndAdd(t, 10);
        System.out.println(t.x);

    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小海海不怕困难

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值