synchronized就在JDK1.6做了锁升级的优化

无锁、匿名偏向:当前对象没有作为锁存在。

偏向锁:如果当前锁资源,只有一个线程在频繁的获取和释放,那么这个线程过来,只需要判 断,当前指向的线程是否是当前线程 。 如果是,直接拿着锁资源走。 如果当前线程不是我,基于CAS的方式,尝试将偏向锁指向当前线程。如果获取不到,触发 锁升级,升级为轻量级锁。(偏向锁状态出现了锁竞争的情况)

轻量级锁:会采用自旋锁的方式去频繁的以CAS的形式获取锁资源(采用的是自适应自旋锁) 如果成功获取到,拿着锁资源走 如果自旋了一定次数,没拿到锁资源,锁升级。

重量级锁:就是最传统的synchronized方式,拿不到锁资源,就挂起当前线程。ObjectMonitor

img

img

AQS就是AbstractQueuedSynchronizer抽象类,AQS其实就是JUC包下的一个基类,JUC下的很多 内容都是基于AQS实现了部分功能,比如ReentrantLock,ThreadPoolExecutor,阻塞队列, CountDownLatch,Semaphore,CyclicBarrier等等都是基于AQS实现

img

acquire方法,是公平锁和非公平锁的逻辑一样

1
2
3
4
5
6
7
8
9
10
11
public final void acquire(int arg) {
// tryAcquire:再次查看,当前线程是否可以尝试获取锁资源
if (!tryAcquire(arg) &&
// 没有拿到锁资源
// addWaiter(Node.EXCLUSIVE):将当前线程封装为Node节点,插入到AQS的双向链表的结尾
// acquireQueued:查看我是否是第一个排队的节点,如果是可以再次尝试获取锁资源,如果长时间拿不到,挂起线 // 如果不是第一个排队的额节点,就尝试挂起线程即可
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 中断线程的操作
selfInterrupt();
}

AQS中为什么要有一个虚拟的head节点

因为Node中存在waitStatus的状态,默认情况下状态为0,如果当前节点的后继节点线程挂起了, 那么就将当前节点的状态设置为-1。这个-1状态的出现是为了避免重复唤醒或者释放资源的问题。因为AQS中排队的Node中的线程如果挂起了,是无法自动唤醒的。需要释放锁或者释放资源后,再 被释放的线程去唤醒挂起的线程。

AQS中为什么选择使用双向链表

ReentrantLock提供了一个方 法,lockInterruptibly方法,也就是线程在竞争锁资源的排队途中,允许中断。中断后会执行 cancelAcquire方法,从而将当前节点状态置位1,并且从AQS队列中移除掉。如果采用单向链表, 当前节点只能按到后继或者前继节点,这样是无法将前继节点指向后继节点的,需要遍历整个AQS 从头或者从尾去找。单向链表在移除AQS中排队的Node时,成本很高。 当前在唤醒后继节点时,如果是单向链表也会出问题,因为节点插入方式的问题,导致只能单向的去 找有效节点去唤醒,从而造成很多次无效的遍历操作,如果是双向链表就可以解决这个问题。