synchronized实现原理
synchronized就在JDK1.6做了锁升级的优化
无锁、匿名偏向:当前对象没有作为锁存在。
偏向锁:如果当前锁资源,只有一个线程在频繁的获取和释放,那么这个线程过来,只需要判 断,当前指向的线程是否是当前线程 。 如果是,直接拿着锁资源走。 如果当前线程不是我,基于CAS的方式,尝试将偏向锁指向当前线程。如果获取不到,触发 锁升级,升级为轻量级锁。(偏向锁状态出现了锁竞争的情况)
轻量级锁:会采用自旋锁的方式去频繁的以CAS的形式获取锁资源(采用的是自适应自旋锁) 如果成功获取到,拿着锁资源走 如果自旋了一定次数,没拿到锁资源,锁升级。
重量级锁:就是最传统的synchronized方式,拿不到锁资源,就挂起当前线程。ObjectMonitor
AQS就是AbstractQueuedSynchronizer抽象类,AQS其实就是JUC包下的一个基类,JUC下的很多 内容都是基于AQS实现了部分功能,比如ReentrantLock,ThreadPoolExecutor,阻塞队列, CountDownLatch,Semaphore,CyclicBarrier等等都是基于AQS实现
acquire方法,是公平锁和非公平锁的逻辑一样
1 | public final void acquire(int arg) { |
AQS中为什么要有一个虚拟的head节点
因为Node中存在waitStatus的状态,默认情况下状态为0,如果当前节点的后继节点线程挂起了, 那么就将当前节点的状态设置为-1。这个-1状态的出现是为了避免重复唤醒或者释放资源的问题。因为AQS中排队的Node中的线程如果挂起了,是无法自动唤醒的。需要释放锁或者释放资源后,再 被释放的线程去唤醒挂起的线程。
AQS中为什么选择使用双向链表
ReentrantLock提供了一个方 法,lockInterruptibly方法,也就是线程在竞争锁资源的排队途中,允许中断。中断后会执行 cancelAcquire方法,从而将当前节点状态置位1,并且从AQS队列中移除掉。如果采用单向链表, 当前节点只能按到后继或者前继节点,这样是无法将前继节点指向后继节点的,需要遍历整个AQS 从头或者从尾去找。单向链表在移除AQS中排队的Node时,成本很高。 当前在唤醒后继节点时,如果是单向链表也会出问题,因为节点插入方式的问题,导致只能单向的去 找有效节点去唤醒,从而造成很多次无效的遍历操作,如果是双向链表就可以解决这个问题。