文章目录
1、类继承关系
接口:Lock,ReadWriteLock
实现:ReentrantLock,ReentrantReadWriteLock
Lock API:JDK提供,在J.U.C的包中
- Lock接口简介
lock方法可以通过自旋tryLock进行实现
2、ReentrantLock
ReentrantLock:可重入锁
2.1、ReentrantLock数据结构与算法
-
数据结构:
owner:相当于synchronized的object monitor中的owner,用于存储当前抢到锁的线程
count:计数器,用于实现可重入功能
waiter:相当于synchronized的object monitor中的EntryList,抢不到锁的线程就会进入waiter,并且线程被挂起 - lock算法:
- 查看count是否为0,如果为null,则CAS(0, 1)修改count,CAS成功后,将owner设置为当前线程(设置owner不需要CAS)
- 如果count不为0,说明等待队列,判断owner是否为当前线程,count自增(count自增不需要CAS)
- 如果以上不满足,则说明抢不到锁,进入waiter,并且挂起当前线程
- unlock算法:
- 判断owner是否为当前线程,如果不是,直接throw IllegalMonitorStateException
- 如果owner是当前线程,count自减,如果count不为0,则锁还没有释放
- 如果count已经为0,则说明锁成功释放,并把owner置为null(可以用CAS),并唤醒waiter中的第一个线程
2.2、Condition
Condition对象可以通过ReentrankLock的newCondition创建,提供await()以及signa()方法,功能类似于wait/notify。
Condition.await()/signal() 与wait/notify的区别:
相同点:
- wait/notify必须在synchronized中使用,wait挂起线程且释放锁
- 通过lock.newCondition()获取Condition对象await()也会挂起线程且释放锁,会被放入ReentrantLock的“WaitSet”
不同点:
- wait/notify,一个synchronized中只有调用一个
- lock接口里面,一个lock可以有多个condition(即ReentrantLock可以多次调用newCondition(),创建多个Condition对象,每个Condition都是独立的,不会相互影响)
3、ReentrantLock与synchronized对比
- 性能上差异:
单线程的情况下synchronized比Lock性能要好 -> 偏向锁,锁消除
上升到重量级锁的时候synchronized和Lock算法一样 -
synchronized:StringBuilder要使用synchronized,大多数情况下在单线程下使用
-
优点:
- 使用简单,语义清晰,哪里需要点哪里
- 由JVM提供,提供了多种优化方案(锁粗化,锁消除,偏向锁,轻量级锁)
- 锁的释放由JVM来完成,不用人工干预,也降低了死锁的可能性
-
缺点:
- 无法实现一些锁的高级功能。ex.公平锁,中断锁,读写锁,共享锁等
-
优点:
-
Lock
-
优点:
- 所有synchronized的缺点
- 可以实现更多功能,让synchronized缺点更多
-
缺点:
- 需要手动解锁unlock,新手使用不当可能造成死锁
-
优点:
4、ReentrantReadWrite
4.1、读写锁算法
- 写锁lock算法:
- 判断readCount是否为0,如果不为0,则说明读锁被占用,线程进入waiter
- 如果readCount为0,则判断writeCount是否为0,如果不为1,则判断owner是否为当前线程,是的话,writeCount自增(写锁重入)
- 如果writeCount为0,则CAS(0, 1),成功说明抢占写锁,并修改owner为当前线程
- CAS不成功,说明没有成功抢锁,则进入waiter等待队列
- 写锁unlock算法:
- 判断owner是否为当前线程,如果是,则writeCount自减
- 判断writeCount是否为0,如果为0,则说明释放写锁成功,设置owner为null。再判断readCount是否为0,如果不为0,则说明锁降级,不唤醒waiter头节点线程;否则唤醒waiter的头节点线程
- 如果writeCOunt不为0,则说明写锁还没有被释放
- 读锁lock算法:读锁抢占成功会唤醒waiter的头节点线程
- 判断writeCount是否为0,如果writeCount为0,则判断readCount是否为0,如果为0,则CAS(0,1),如果不为0,则readCount自增,且唤醒waiter的头节点线程
- 如果writeCount不为0,则判断owner是否为当前线程,如果为当前线程,则readCount自增,且唤醒waiter的头节点线程
- 如果writeCount不为0,且owner不为当前线程,则进入waiter并挂起当前线程
- 读锁unlock算法:
- readCount自减,判断readCount是否为0,如果为0,说明读锁释放成功,唤醒waiter头节点线程
- 如果readCount不为0,则说明仍有其他线程在占用读锁,不进行其他操作
- 锁降级:(读写锁,没有锁升级,读取了读锁之后,不可以读取写锁)
- Thread1加写锁 -> 写锁
- Thread1加读锁 -> 写锁,读锁
- Thread1释放写锁 -> 读锁(由写锁降级为读锁,其他线程可以加读锁)
4.2、AQS 抽象队列同步器
AQS根据ReentrantReadWriteLock算法,使用模板方法进行了实现。子类可以通过复写AQS的各个方法,实现不同的功能。
(try的方法都由子类进行实现)
- tryAcquire == 写锁tryLock
- tryRelease == 写锁tryUnlock
- tryAcquireShared == 读锁tryLock
- tryReleaseShared == 读锁tryUnlock
ps. AQS通过一个state实现了上述的readCount和writeCount,用前16位实现了readCount,后16位实现writeCount。目的是保证原子性操作。
通过AQS实现的并发工具类:
- CountDownLatch
- Semaphore计数信号量
- CyclicBarrier:循环栅栏
- FutureTask:用于获取线程的执行结果