reentrant 英[riːˈɛntrənt] 美[ˌriˈɛntrənt] 先学会读。单词原意是可重入的html
- 考察显示锁的使用。可延伸知识点
- 独占锁 & 共享锁
- 独占锁 - 悲观锁(不能同时被多个线程持有 - synchronized锁 & ReentrantLock)
- 共享锁 - 乐观锁(ReentrantReadLock )
- 读共享、写排他
- 重入锁
- 方法进行深层次调用时,获取同一把锁可以获取到,不会死锁
- 使用范式
- finally
Lock lock = new ReentrantLock();
lock.lock();
try{
//一顿操做
}finally {
lock.unlock();
}
- CAS原理实现乐观锁
- 原理 : 在线程对数据进行修改时,须要对比如今持有的变量和原始地址中的值是否相同,若相同则替换成功,若不一样替换失败
- 实现乐观锁 = 自旋 + CAS
- 问题 :
- ABA问题 - jdk AtomicStampedReference AtomicMarkableReference
- 自旋时间过长会致使CPU消耗过大
- 一次操做只能修改一个内存地址的变量 AtomicReference<V>
- jdk相关实现类
- AtomicInteger
- AtomicReference<V> 多个共享变量一块儿操做
- AtomicReferenceArray 操做时是复制了原数组一份,修改后原数组的值不变
- AtomicMarkableReference 解决ABA问题,但只关心是否改变
- AtomicStampedReference 解决ABA问题,会记录改变次数.可经过getStamp()获取
- condition - wait¬ify --> ***
- CHL队列锁 - 基于链表
- 每一个线程拿锁时建立一个Node,locked状态置为true,把本身放到链表的tail,而后把myPred指向以前的tail,以后向前循环check locked状态,直到为false时本身就拿到了锁
- AQS - 抽象队列同步器
- CHL实现的变种 -- 双向链表
- 实现多线程访问共享资源的同步框架FIFO
- 采用模板方法,一些方法须要继承者实现,但为何不设计成abstract方法(抽象方法都要实现,为开发者考虑,独占式获取锁只实现独占方法,共享方式只实现共享方法)
- 独占式 tryAcquire、 tryRelease、isHeldExclusively
- 共享
tryAcquireShared
、tryReleaseShared
、isHeldExclusively
- 方法都有一个参数 ?
- state属性
- volatile int state 表明共享资源
- 提供三种访问方式 getState()、setState()、compareAndSetState()
- setState 和 compareAndSetState 有什么区别 ? 前者不是有安全问题吗,为何还存在。(setState 是在已经拿到锁的状况下调用,不会有安全问题)
- ReentrantLock 用来标记拿锁的次数、CountDownLatch 用来标记任务的个数
- acquire()方法
- tryAcquire()
- addWaiter() 默认独占式 - 自旋添加节点
- acquireQueued() 真正的拿锁方法 返回等待过程当中是否被中断过,自旋+阻塞获取资源
- 若前驱结点是head且拿到了锁的状况下,把当前节点置为head节点,并把原head节点脱离
- shouldParkAfterFailedAcquire(Node, Node) 返回前驱节点是否处于等待状态Node.SIGNAL。并将本身放在此节点的后置节点
- parkAndCheckInterrupt() 返回是否被中断过。阻塞当前线程,若是线程被唤醒,检查是被打断仍是被正常唤醒
- 先去尝试拿锁,拿不到就将本身放在等待队列尾部,而后自旋向前寻找,直到head节点拿到锁为止。即便中间被打算,等待过程也不会中断。而是在拿到锁以后再中断本身
- release()方法
- tryRelease(arg)
- 释放锁成功以后调用 unparkSuccessor(head) -> 将head节点状态置为0,用unpark()唤醒等待队列中最后边的那个未放弃线程
- 唤醒acquireQueued方法中的判断,让线程拿到锁
- acquireShared()方法
- 相似acquire()
- 多一个setHeadAndPropagate()方法。在拿到资源的时候向后唤醒 - 体现共享
- 调用了doReleaseShared()
- releaseShared()
- doReleaseShared()释放掉资源后,唤醒后继 实际调用unparkSuccessor(head)
- 公平锁&非公平锁
- 遵循拿锁的顺序
- 隐式锁synchronized 对比
- lock提供一些除lock()操做以外功能,更加灵活
- 非特殊状况下使用synchronized,jdk对它的优化比较大
- 模板方法
- 实现
- ReentrantLock自身并未继承AQS,而是采用内部类Sync继承。屏蔽内部实现、外部调用者不用关心具体细节
- 如何实现可重入
- tryAcquire if(state ==0 )的else中进行当前线程锁的累加
- 公平锁和非公平锁有什么区别
- 非公平锁再获取锁的时候不按排队顺序而是随机拿锁
- tryAcquire方法中!hasQueuedPredecessors() 来断定队列中是否有前驱节点在等待锁
- 在阻塞以前,线程都会经过shouldParkAfterFailedAcquire去修改其前驱节点的waitStatus=-1。这是为何?为了release时unparkSuccessor(head) 唤醒后续节点
- unparkSuccessor时为何会出现s==null || s.waitStatus>0的状况,这种状况下,为何要经过prev指针反向查找Successor节点?
- s == null 是由于acquireQueued() 在拿到锁以后会将head.next = null .这样链表就断了,因此要从尾部节点向前找
- s > 0 是在cancel的时候,节点在head节点的后继节点断开。致使链表断裂。
总结: 如何回答这个问题。数组
1. ReentrantLock 经过定义一个内部类来实现Lock接口,公平锁和非公平锁的实现都是基于这个继承自AQS的内部类Sync。不一样的点是在拿锁的时候,非公平锁会直接拿锁,而公平锁会判断队列中是否还有线程在等待锁。若是有就会拿锁失败。安全
2.后面就是回答AQS原理了多线程
参考AQS http://www.javashuo.com/article/p-xcevmtwv-gz.html 框架
图解 https://www.jianshu.com/p/b6efbdbdc6fa优化