AQS的傀儡之Lock锁

源于蚂蚁课堂的学习,点击这里查看(老余很给力)    

       Java锁的武林纷争中,存在两大阵营。分别是以Synchronized为首的Java内置关键字对象,自动维护锁的持有和释放,还有就是以Lock锁为代表的Java自定义锁。在之前的博文中,帝都的雁已为大家介绍了Synchronized派系的渊源,本文则重点聊聊Lock锁的那些花边新闻。

       Lock锁,是Java并发包下的一个类,其API的使用功能在本文不做介绍,感兴趣的读者可自行阅读源码或查阅相关资料。本文重点介绍Lock锁底层实现原理(仅概括设计原理,源码级别的剖析会在后续文章做更新)。

 

一、AQS(AbstractQueuedSynchronizer)

直译为抽象同步队列。主要封装了Java并发包下很多公共的算法逻辑,而Lock锁本质上来说就是AQS的一个宿主,内部的任何操作都是靠AQS的运转。

AQS中有几大属性,而并发包下很多工具类都是基于AQS的加锁解锁的原理,以及属性的运用来巧妙实现自身业务功能。

1、int state

状态值,用于标识锁的一些信息,AQS底层在通过CAS自旋比较时,就是比较此属性是否发生预期变化;也会记录lock锁重入次数。

2、Node head

AQS阻塞队列中的头节点,也就是存放当前锁持有线程的节点,但这个头节点的很多信息都是空的,主要为了方便GC回收。

3、Node tail

AQS阻塞队列中的尾点,未抢到锁的线程会放入阻塞队列的末尾。

二、读写锁

Lock锁的创建方式有多种,支持创建读锁ReentrantReadWriteLock.readLock和写锁ReentrantReadWriteLock.writeLock。对于熟悉锁概念的读者一定明白,读读可以共享,其他情况下资源是不会共享的。Lock的读写锁也很好地体现了这一点。

细心的读者在阅读Lock锁源码时发现,无论是哪一种Lock的实现类,内部都会维护一个sync的内部类去继承AbstractQueuedSynchronizer(简称AQS)类,而它加锁和解锁也都是在AQS中实现模板方法。

三、重入锁

Lock lock = new ReentrantLock();

创建的重入锁,支持锁的重入性,即当前线程拿到锁后,遇到这把锁的加锁操作,会进行重入,这也是基于AQS的属性state去做对应的标记。即锁重入一次,state会自增一次。

四、公平锁和非公平锁

Lock锁的实现类ReentrantLock内部在进行对锁支配时,通过内部类Sync操作,这个类有两个子类。

1、非公平锁NonfairSync

ReentrantLock默认创建的就是非公平锁。

当多个线程抢夺锁资源时,不管抢到与否,都会放入到AQS的阻塞队列中(类似于Synchronized的锁池)。只不过抢到锁的线程位于阻塞队列头部,且内容为空。设想,如果当这个线程执行完业务,释放锁时,按照队列原则,队列第二个位置的线程理论上应该获取这把锁,但这时非阻塞队列中的其他线程来了,进行锁资源的抢夺,且抢成功,那么对于排队的线程来说,是不是就不公平了?这就是非公平锁的设计思想。

2、公平锁FairSync

对比非公平锁,公平锁对抢到锁资源的线程做了判断,如果不是阻塞队列头部那个理论上应该获取到锁的线程的话,就会把这个线程阻塞,并放入阻塞队列的末尾,然后继续让头部进行锁资源的抢夺。

五、加锁原理

不管锁是否公平,其加锁和释放锁的操作都是一致的。加锁时,通过CAS比较state是否为预期状态,如果是则设置锁的持有线程,执行业务代码;若不是,则也会在CAS自旋一次,最后通过LockSupport.park去将当前线程阻塞,封装为Node节点,放入AQS的双向链表末尾中等待锁资源。

六、解锁原理

解锁时会判断解锁线程和锁持有线程是否为一个线程,然后将阻塞队列的第二个节点信息向后变更,同时改变锁持有线程的信息,通过LockSupport唤醒锁持有线程。

七、Condition

类似于Synchronized的等待池,是AQS的一个内部类。

Condition condition = lock.newCondition();

lock实现类似sync中的wait和notify方法的工具。

当condition.await()时,会将当前线程放入Condition的单向阻塞列表中,LockSupport阻塞,直到调用condition.signal()才会从阻塞列表中取出LockSupport唤醒。

八、AQS与Lock的关系

Lock锁本质上就是AQS的具体应用,AQS封装了并发包下常见的公共方法,通过对这些方法的重写以及对AQS的属性灵活运用,可以很好地扩展各种业务功能。

九、Lock与Synchronized区别

个人理解,Lock和Synchronized几乎设计的原理以及组件都是类似的。

1、相同点

都有锁的短暂自旋去减少线程阻塞(锁的升级膨胀)。

对于未获取锁的线程,都有一个双向列表存放。

对于主动调用锁对象阻塞方法的线程,都会存放在一个单向链表中。

都支持锁的重入。

2、不同点

Lock的开销是我们自己控制的,使用上更为灵活,而且支持对其功能进行扩展;但Synchronized是内置的关键字,所以其维护依赖于JDK。

锁升级膨胀的具体行为不同,lock没有偏向锁的概念,只会通过CAS自旋比较一次;Synchronized则是在对象头的MarkWord中记录锁的偏向信息,然后通过CAS比较,比较多次之后才会阻塞线程。

欢迎大家和帝都的雁积极互动,头脑交流会比个人埋头苦学更有效!共勉!

公众号:帝都的雁

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中的AQS(AbstractQueuedSynchronizer)是实现和同步器的一种重要工具。在AQS中,一个节点表示一个线程,依次排列在一个双向队列中,同时使用CAS原子操作来保证线程安全。当多个线程对于同一资源竞争时,一个节点会被放置在队列的尾部,其他线程则在其之前等待,直到该资源可以被定。 当一个线程调用lock()方法进行定时,它会首先调用tryAcquire()方法尝试获取。如果当前资源尚未被定,则该线程成功获取,tryAcquire()返回true。如果当前资源已被定,则线程无法获取,tryAcquire()返回false。此时该线程就会被加入到等待队列中,同时被加入到前一个节点的后置节点中,即成为它的后继。然后该线程会在park()方法处等待,直到前一个节点释放了,再重新尝试获取。 在AQS中,当一个节点即将释放时,它会调用tryRelease()方法来释放,并唤醒后置节点以重试获取。如果当前节点没有后置节点,则不会发生任何操作。当一个线程在队列头部成功获取和资源时,该线程需要使用release()方法释放和资源,并唤醒等待队列中的后置节点。 总之,AQS中的机制是通过双向等待队列实现的,其中节点表示线程,使用CAS原子操作保证线程安全,并在tryAcquire()和tryRelease()方法中进行定和释放。该机制保证了多线程环境下资源的正确访问和线程的安全执行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值