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