锁分类
1. AQS(抽象队列同步器)
同步队列(即阻塞队列,线程处于blocked状态,头结点是获取了锁的节点)和等待队列(线程处于waiting状态),等待队列的节点/线程被执行signal()加入同步队列
公平锁只有同步队列的第一个节点能获取锁,非公平锁只有第一个或尚未加入的能获取锁;公平锁和非公平锁区别在于判断当前节点是否有前置节点
锁实际上是通过内部类Sync(即同步器;子类FairSync和NonfairSync)实现,而Sync继承自AbstractQueuedSynchronizer(AQS),AQS通过内部类Node(同步队列和等待队列的节点)和ConditionObject(条件获取锁)来实现锁的功能
应用
reentrantlock,CountDownLatch,CyclicBarrier,Semaphore中使用
加锁解锁流程
获取锁:
(ReentrantLock.lock()调用sync.acquire(1);acquire先tryAcquire,检查有无前置节点(公平锁),cas修改state状态;若成功则获取锁成功并返回;若失败则调用addWaiter加入同步队列,然后acquireQueued会判断addWaiter添加的节点如果是前驱节点是头节点,并且能够获得同步状态的话,当前线程能够获得锁该方法执行结束退出;否则先将节点状态设置成SIGNAL,然后调用LookSupport.park方法使得当前线程阻塞。)
(获取失败加入同步队列)
释放锁:
释放指定量的资源,如果彻底释放了(即state=0),它会唤醒同步队列里的其他线程来获取资源。(ReentrantLock.unlock()调用sync.release(1);release()先更新state,若更新后为0,则获取head节点,调用unparkSuccessor(h);unparkSuccessor调用LockSupport.unpark(head的next节点.thread)唤醒同步队列的下一节点,使其尝试获取锁)
(唤醒同步队列下一节点,从同步队列取出)
Node节点状态(waitStatus):
1. CANCELLED:值为1,在同步队列中等待的线程等待超时或被中断,需要从同步队列中取消该Node的结点,其结点的waitStatus为CANCELLED,即结束状态,进入该状态后的结点将不会再变化。
2. SIGNAL(同步队列中):值为-1,被标识为该等待唤醒状态的后继结点,当其前继结点的线程释放了同步锁或被取消,将会通知该后继结点的线程执行。说白了,就是处于唤醒状态,只要前继结点释放锁,就会通知标识为SIGNAL状态的后继结点的线程执行。
3. CONDITION(等待队列中):值为-2,与Condition相关,该标识的结点处于等待队列中,结点的线程等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中, 等待获取同步锁。
4. PROPAGATE:值为-3,与共享模式相关,在共享模式中,该状态标识结点的线程处于可运行状态。
加入等待队列/条件队列
condition是要和lock配合使用的也就是condition和Lock是绑定在一起的
使用场景
阻塞队列BlockingDeque
加入等待队列/条件队列
condition.await()方法后会使得当前获取lock的线程进入到等待队列,直至被signal/signalAll后会使得当前线程从等待队列中移至到同步队列中去,直到获得了lock后才会从await方法返回,或者在等待时被中断会做中断处理。
从等待队列中取出
调用condition的signal或者signalAll方法可以将等待队列中等待时间最长的节点移动到同步队列中,使得该节点能够有机会获得lock。
同步队列为什么是双向链表,而等待队列是单链表?
在队列同步器中,头节点是成功获取到同步状态的节点,而头节点的线程释放了同步状态后,将会唤醒其他后续节点,后继节点的线程被唤醒后需要检查自己的前驱节点是否是头节点,如果是则尝试获取同步状态。
final