其他分享
首页 > 其他分享> > M9. 锁 AQS

M9. 锁 AQS

作者:互联网

█ 1 可重入锁(递归锁)

同一个线程在外层方法获取锁的时候,在进入该线程的内层方法 会自动获取锁。

锁对象是同一个对象。

ReentrantLock(显式) synchronized(隐式)都是可重入锁

避免死锁

█ 2 LockSupport

他是线程等待唤醒机制wait notify的加强版

其中的 park() unpark()的作用是 阻塞线程和接触阻塞线程


○ 3种让线程等待和唤醒的方法:

图片

○ 2.1 object类中的wait notify

实现线程等待和唤醒

同一个代码块 必须用synchronized 否则 IllegalMonitorStateException

await notify 位置反转 无法唤醒


○ 2.2 Condition接口中的await signal

实现线程等待和唤醒

同一个锁块 否则 IllegalMonitorStateException

await signal 位置反转 无法唤醒

○ ○ 2.2.. 综上 传统的synchronized Lock

实现线程等待和唤醒 约束:

1)线程先要获得并持有锁,必须在锁块中

2)必须要先等待再唤醒

○ 2.3 LockSupport类中的park等待和unpark唤醒:

不需要上面的

图片

图片

○ 2.4 why可先unpark唤醒后阻塞线程?

因为unpark()获得了一个凭证,之后调用park方法,剋要消费掉凭证,所以不会阻塞。

代码

图片

○ 2.5 why唤醒unpark两次后阻塞两次,结过阻塞线程?

凭证的数量最多是1,连续调用两次unpark和调用一次 unpark效果一样,只会增加一个凭证;而调用两次park需要消费2个凭证,证不够,阻塞


两个unpark()一起调用的时候会阻塞,因为第一个凭证被消耗了,第二个没有拿到凭证

图片

█ 3.AQS 框架

他是用来构建锁 或者其他同步器组件 的 重量级 基础框架 及整个 JUC体系的基石,,,

AQS = state + CLH队列

图片

1 通过内置的FIFO队列 来完成资源获取线程的 排队工作,
2 并通过一个int类型变量 表示 持有锁的状态 。


加锁会导致阻塞,排队等候机制;

阻塞唤醒机制 主要用 CLH队列的变体实现,把暂时 获取不到锁的线程 加入到队列。封装成节点,通过CAS、自旋 和LockSupport.park()方式,维护state变量的状态,实现同步控制。

AbstractQueuedSynchronizer抽象的队列同步器

跟AQS有关的:ReentrantLock\CountDownLatch\ReentrantReadWriteLock\Semaphore 继承了AbstractQueuedSynchronizer


○ 3.1 JUC同步队列CLH

CLH队列,,AQS中的队列是CLH变体的 虚拟双向队列FIFO pre next Node(包了线程)

AQS的CLH队列有head和tail,另外有一个空的占位头节点(哨兵节点),最开始这个头节点的尾指针指向null

哨兵节点他的waitState=-1,当ThreadB被unpack出队之后,原来的哨兵早已被回收,现在的哨兵就是B在的位置,Node改成了null

○ 3.2 源码流程:

相似的:HashMap里面的KV键值对 是先被封装成了Node


流程:先看这个锁是否被占用,如果被占了就acquire(),如果当前CLH队列是空的,就添加哨兵节点和这个线程封装的Node;如果CLH不是空的就直接添加,一直比较状态码,看他的状态;
如果空了,CLH哨兵节点后面第一个安排出队 占用锁,当前的哨兵被GC,存放出队线程Node的位置变成哨兵节点。


函数有:

lock() 

aquire()   tryAquire(arg)   addWaiter(Node.EXCLUSIVE) acquireQueued(addWaiter(Node.EXCLUSIVE),arg)

unlock() sync.release(1) tryRelease() unpark() park被解除

AbstractQueuedSynchronizer 包括 Node head,tail ;volatile int state <0没人>;

Node里面有pre next Node ; volatile int waitStatus; Node有共享模式SHARED 和排他EXCLUSIVE

图片

图片

3.2.1 lock()

图片

3.2.2 aquire() tryAquire(arg) addWaiter(Node.EXCLUSIVE) acquireQueued(addWaiter(Node.EXCLUSIVE),arg)

图片

CLH队列有head和tail,另外有一个空的占位头节点,最开始这个头节点的尾指针指向null

哨兵节点他的waitState=-1,当ThreadB被unpack出队之后,原来的哨兵早已被回收,现在的哨兵就是B在的位置,Node改成了null

图片图片图片

3.2.3 unlock() sync.release(1) tryRelease() unpark() park被解除

非公平ReentrantLock,走lock(),false 就aquire尝试获取锁tryAquire ,如果获取不到,就排队aquireQueued,

对于排队addWaiter,要先队列初始化,enq哨兵节点

封装Node,加入。

█ 5.锁

○ 5.1 synchronized和lock的区别:

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition(); condition.await();.signalAll(); 

○ 5.2 锁的种类

公平锁 非公平锁/可重入锁(又名递归锁)/独占锁 共享锁/自旋锁
公平锁: 先来后到。FIFO


非公平锁:优先级。ReentrantLock默认是false 不公平.;;;;;synchronized
缺点:优先级反转 // 饥饿现象
优点: 吞吐量比公平锁大。


可重入锁:ReentrantLock synchronized 外层有锁,内层自动也有锁,,线程可以进入有锁的同步代码块。对应的线程是同一个线程
优点:避免死锁


自旋锁:循环的去尝试获取锁
缺点:循环消耗CPU
优点:减少线程上下文切换的消耗
代码:两个线程强,一个调用mylock不是null了,另一个只能等
AtomicReference<Thread> void mylock{while(!automicReference.compareAndSet()){}}


独占锁:一个锁只能被一个线程持有


共享锁:多个线程持有。ReentrantReadWriteLock 读锁是共享锁,写锁是独占锁
写操作:原子+独占
.lock() .unlock() .

○ 5.3 死锁

争抢资源 互相等待。
代码:自己持有锁,还想要别人的

○ ○ 5.3.1 产生死锁的原因:

1.系统资源不足 // 2.资源分配 // 3.进程运行顺序不合适

○ ○ 5.3.2 产生死锁的四个必要条件:

1、互斥条件:一个资源每次只能被一个进程使用。

2、请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

3、不剥夺条件: 进程已获得的资源,在末使用完之前,不能强行剥夺。

4、循环等待条件: 若干进程之间形成一种头尾相接的循环等待资源关系。

○ ○ 5.3.3 解决方式 命令行:

jps -l  查看pid
jstack pid

○ ○ 5.3.4 避免死锁

1、按同一顺序访问对象。

2、避免事务中的用户交互。

3、保持事务简短并处于一个批处理中。

4、使用较低的隔离级别。

5、使用基于行版本控制的隔离级别。

6、使用绑定连接。

█ 6.CountDownLatch/CyclicBarrier/Semaphore

CountDownLatch 一个一个减
CountDownLatch countDownLatch= new CountDownLatch(7);先countDown(),再await()
await(),调用线程会阻塞,其他线程调用 xxx.countDown();计数器会-1 不会阻塞,
变为0是,await阻塞的会被唤醒。


CyclicBarrier 集齐召唤 全部到达屏障之后开门
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{Syop}); await()



Semaphore信号量:1.用于多个共享资源互斥使用;2.用于并发编程数的控制。
Semaphore semaphore= new Semaphore(7) ; semaphore.acquire(); semaphore.release();

标签:Node,CLH,AQS,队列,阻塞,unpark,线程,M9
来源: https://www.cnblogs.com/ming-michelle/p/14657111.html