ReentrantLock是可重入的独占锁,同步通过实现AQS(抽象的队列式同步器,内部定义了一套多线程访问共享资源的的同步框架),锁的竞争依靠CAS和Unsafe 。
ReentrantLock使用:
new一个lock对象,调用lock方法获取到锁,在finally代码块调用unlock方法释放锁,如果不在finally代码块释放,代码发生异常会导致锁未能正常释放,造成死锁。
方法说明:
ReentrantLock主要方法实现于Lock
lock() : 获取锁
lockInterruptibly() : 获取锁,可中断
tryLock() : 尝试获取锁
tryLok(long time , TimeUnit unit) : 尝试获取锁,可设置过期时间
unlock() : 释放锁
newCondition() : 构建条件对象,如synchronized的mutex对象的await和notify等功能
源码说明
ReentrantLock自定义sync集成AQS
ReentrantLock内部提供了公平锁和非公平锁,通过源码可知通过无参构造函数创建对象默认使用公平锁,非公平锁在创建对象传入false即可。
公平锁与非公平锁的公平性主要体现在锁的竞争上
公平锁:线程获取锁的时候需添加到同步队列末尾,等待获取
非公平锁:线程获取锁的时候可以直接跟当前同步队列head节点进行CAS竞争获取锁,获取失败后再添加到同步队列末尾,依次等待获取
锁是如何竞争的?
锁的竞争实际上是对AQS同步框架内state原子变量的获取修改,使用unsafe类的CAS(compareAndSwap)修改成功则表明获取到锁,unsafe和CAS不在此展开讲解。
公平锁和非公平锁取锁过程都实现了AQS的抽象方法tryAcquire方法,传入参数都为1。
公平锁和非公平锁都是先获取AQS框架中的原子变量state,如果是state==0就通过CAS尝试将state从0改为1,setExclusiveOwnerThread是设置当前线程为独占锁拥有者,else代码块我们还可以发现如果当前线程是独占锁拥有者则可以直接获取锁,不需要重复排队获取,将state+acquires即可。从这里我们知道ReentrantLock是可重入的锁,(state-1)的数值就是锁重入的次数。
非公平锁相较于公平锁除了在调用lock方法时可以直接参与state的修改之外,在tryAcquire方法代码中还可以看出区别。
公平锁代码中调用hasQueuedPredecessors方法,该方法主要判断当前节点是否除了head节点外还有其他节点等待获取锁
1:h != t 说明有独立于head的tail节点 ,代码结果为true,线程不能尝试锁的竞争
2:h.next == null,在AQS框架的源码中,当同步队列head获取到锁之后,会立即将next的节点设置为新的head节点并且执行next == null(GC回收),还是相当于h == t 。由于在高并发情况下多线程对数据的读取会存在不一致,如果h.next == null 说明独立于head的tail节点已经成为新的head节点,代码结果为true
3:s.Thread != Thread.currentThread(),如果当前是线程是锁的拥有者,则可以立即重入锁,无需排队
概括说明:当前只有一个节点,则可以立即竞争锁,如有多个节点则判断是否可以重入,绕过排队。
锁的释放
公平锁和非公平锁释放锁的过程都是一样的,无论当前锁是否有重入,释放锁的次数都必需等于获取锁的次数,每次释放state都会减去1,只到等于0为止。release是AQS的方法,当锁释放完毕之后,AQS会唤醒head节点去获取锁。
等待条件Condition
condition的作用是对锁进行更准确的控制
await : 线程阻塞等待,相当于Object的await方法
await(long time,TimeUnit unit) : 线程有时间的阻塞等待,相当于Object的await(long time)
signal : 线程唤醒,相当于Object类的notify
signalAll :唤醒所有await阻塞的线程,相当于Object类的notifyAll
直接使用ReentrantLock对象newCondition方法创建即可。