「javaaqs原理」java equals原理

博主:adminadmin 2022-12-14 09:51:06 89

本篇文章给大家谈谈javaaqs原理,以及java equals原理对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。

本文目录一览:

ReentrantLock的底层实现原理

ReentrantLock主要利用CAS+AQS队列来实现。它支持公平锁和非公平锁,两者的实现类似。

CAS:Compare and Swap,比较并交换。CAS有3个操作数:内存值V、预期值A、要修改的新值B。 当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则重新获取内存地址V的当前值,并重新计算想要修改的值(重新尝试的过程被称为自旋) 。修改变量的操作是一个原子操作,要么完成修改,要么完全没改;CAS被广泛的应用在Java的底层实现中。 在Java中,CAS主要是由sun.misc.Unsafe这个类通过JNI调用CPU底层指令实现

AbstractQueuedSynchronizer简称AQS,是一个用于构建锁和同步容器的框架。事实上concurrent包内许多类都是基于AQS构建,例如ReentrantLock,Semaphore,CountDownLatch,ReentrantReadWriteLock,FutureTask等。AQS解决了在实现同步容器时设计的大量细节问题。

AQS使用一个FIFO的队列表示排队等待锁的线程,队列头节点称作“哨兵节点”或者“哑节点”,它不与任何线程关联。其他的节点与等待线程关联,每个节点维护一个等待状态waitStatus

ReentrantLock的基本实现可以概括为: 先通过CAS尝试获取锁。如果此时已经有线程占据了锁,那就加入AQS队列并且被挂起。当锁被释放之后,排在CLH队列队首的线程会被唤醒,然后CAS再次尝试获取锁。 在这个时候,如果:

非公平锁 :如果同时还有另一个线程进来尝试获取,那么有可能会让这个线程抢先获取;

公平锁 :如果同时还有另一个线程进来尝试获取,当它发现自己不是在队首的话,就会排到队尾,由队首的线程获取到锁。

AQS锁的原理

synchronized是JVM层面实现的锁,而AQS是JDK层面实现的锁。

关于synchronized锁,可以看之前写的这篇:

synchronized锁关键字的膨胀性与用法

其实AQS和synchronized在实现锁的原理上是一样的,只是AQS是借助了同步队列去进行自旋和阻塞,利用条件队列去实现Object的对象方法,去完成等待和唤醒。

我们要注意的是同步队列是等待获取锁的队列,条件队列是曾经获取到锁,但是因为类似(满队入队/空队出队)这样的阻塞行为,而避免一直阻塞,进行调节使用的。

废话不多说,我们从一个实际的场景出发去讲一下这个原理。

1、线程A,B,C想要抢占一个资源做操作;

2、线程A先得头筹获得了对象的锁。在它持有资源的同时,其他线程就会被阻塞,依次加入到同步队列中去,顺序为B,C;

3、每一个Node入队都会自旋检查自己前一个节点是不是signal状态,如果是signal状态,就阻塞自己,等待唤醒;如果不是,就自旋【尝试获取锁,获取不到就尝试将它变成signal状态】这个过程;

4、当线程A执行完毕后,它会释放锁,然后唤醒同步队列的头结点,这时候头结点B就会去获取锁;

5、当B获得锁之后,他发现之前线程A的操作让当前资源队列的元素为空,而它要做的操作是一个take()操作。无法执行,会一直阻塞。要怎么办呢?

6、这时候就会把它添加到条件队列当中,然后释放锁,让其他线程获取资源,直到满足线程B执行操作的条件为止;

7、释放锁之后,唤醒了同步队列的头结点C,此时C获得了资源。它对着资源队列执行了put()操作;

8、此时,条件队列中的节点B将被唤醒,从条件队列转移到同步队列的队尾中去;

9、等线程C执行完put()操作,释放锁,然后唤醒了同步队列中的第一个节点B;

10、然后线程B执行take()操作。并发结束。

以上就是一个简单的锁模型。

对于锁的状态state,是一个int值。

还有一个超时时间。这些是子类自己去实现规则。

同步队列和条件队列的元素都是Node

Node中主要有这些参数:

waitStatus 当前节点的状态。主要有0(初始化),1(取消),-1(SINGAL)【同步队列】,-2(CONDITION)【条件队列】,-3(PROPAGATE )【共享锁】

是排他锁还是共享锁是根据Node中的字段标记的。

nextWaiter,在条件队列中,指下一个节点;在同步队列中,指什么属性的锁。

Thread 绑定的当前线程。

J.U.C|同步队列(CLH)

在上篇我们聊到AQS的原理,具体参见 《J.U.C|带你走进AQS的内心世界》 。

这篇我们来给大家聊聊AQS中核心同步队列(CLH)。

同步队列

一个FIFO双向队列,队列中每个节点等待前驱节点释放共享状态(锁)被唤醒就可以了。

AQS如何使用它?

AQS依赖它来完成同步状态的管理,当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。

Node节点面貌?

CLH同步队列的结构图

这里是基于CAS(保证线程的安全)来设置尾节点的。

如上图了解了同步队列的结构, 我们在分析其入列操作在简单不过。无非就是将tail(使用CAS保证原子操作)指向新节点,新节点的prev指向队列中最后一节点(旧的tail节点),原队列中最后一节点的next节点指向新节点以此来建立联系,来张图帮助大家理解。

源码

源码我们可以通过AQS中的以下两个方法来了解下

addWaiter方法

先通过addWaiter(Node node)方法尝试快速将该节点设置尾成尾节点,设置失败走enq(final Node node)方法

enq

通过“自旋”也就是死循环的方式来保证该节点能顺利的加入到队列尾部,只有加入成功才会退出循环,否则会一直循序直到成功。

上述两个方法都是通过compareAndSetHead(new Node())方法来设置尾节点,以保证节点的添加的原子性(保证节点的添加的线程安全。)

同步队列(CLH)遵循FIFO,首节点是获取同步状态的节点,首节点的线程释放同步状态后,将会唤醒它的后继节点(next),而后继节点将会在获取同步状态成功时将自己设置为首节点,这个过程非常简单。如下图

设置首节点是通过获取同步状态成功的线程来完成的(获取同步状态是通过CAS来完成),只能有一个线程能够获取到同步状态,因此设置头节点的操作并不需要CAS来保证,只需要将首节点设置为其原首节点的后继节点并断开原首节点的next(等待GC回收)应用即可。

聊完后我们来总一下,同步队列就是一个FIFO双向对队列,其每个节点包含获取同步状态失败的线程应用、等待状态、前驱节点、后继节点、节点的属性类型以及名称描述。

其入列操作也就是利用CAS(保证线程安全)来设置尾节点,出列就很简单了直接将head指向新头节点并断开老头节点联系就可以了。

javaaqs原理的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于java equals原理、javaaqs原理的信息别忘了在本站进行查找喔。

The End

发布于:2022-12-14,除非注明,否则均为首码项目网原创文章,转载请注明出处。