自己手动实现一个简易的ReentrantLock和ReentrantReadLock
ReentrantLock
简介
ReentrantLock也称为重入锁,顾名思义,就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。除此之外,该锁的还支持获取锁时的公平和非公平性选择。
可重入锁的概念
重进入是指任意线程在获取到锁之后能够再次获取该锁而不会被锁所阻塞,该特性实现需要解决两个问题。
- 线程再次获取锁。锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次成功获取。
- 锁的最终释放。线程重复n次获取了锁,随后在第n次释放该锁后,其他线程能够获取到该锁。锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示锁已经成功释放。
公平锁与非公平锁
- 在公平的锁中,如果有另一个线程持有锁或者有其他线程在等待队列中等待这个所,那么新发出的请求的线程将被放入到队列中
- 在非公平锁上,只有当锁被某个线程持有时,新发出请求的线程才会被放入队列中(此时和公平锁是一样的)。
- 所以,它们的差别在于非公平锁会有更多的机会去抢占锁。
手动实现Reentrantlock代码示例
public class MartionReentrantLock implements Lock {
//标记重入次数的count值
AtomicInteger count = new AtomicInteger(0);
//锁的拥有者
AtomicReference<Thread> owner = new AtomicReference<>();
//等待队列
private LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();
@Override
public boolean tryLock() {
//判断count是否为0,若count!=0,说明锁被占用
int ct = count.get();
if (ct !=0 ){
//判断锁是否被当前线程占用,若被当前线程占用,做重入操作,count+=1
if (owner.get() == Thread.currentThread()){
count.set(ct + 1);
return true;
}else{
//若不是当前线程占用,互斥,抢锁失败,return false
return false;
}
}else{
//若count=0, 说明锁未被占用,通过CAS(0,1) 来抢锁
if (count.compareAndSet(ct, ct +1)){
//若抢锁成功,设置owner为当前线程的引用
owner.set(Thread.currentThread());
return true;
}else{
//CAS操作失败,说明情锁失败 返回false
return false;
}
}
}
@Override
public void lock() {
//尝试抢锁
if (!tryLock()){
//如果失败,进入等待队列
waiters.offer(Thread.currentThread());
//自旋
for (;;){
//判断是否是队列头部,如果是
Thread head = waiters.peek();
if (head == Thread.currentThread()){
//再次尝试抢锁
if (!tryLock()){
//若抢锁失败,挂起线程,继续等待
LockSupport.park();
}else{
//若成功,就出队列
waiters.poll();
return;
}
}else{
//如果不是,就挂起线程
LockSupport.park();
}
}
}
}
@Override
public void unlock() {
if (tryUnlock()){
Thread th = waiters.peek();
if (th !=null){
LockSupport.unpark(th);
}
}
}
public boolean tryUnlock(){
//判断,是否是当前线程占有锁,若不是,抛异常
if (owner.get() != Thread.currentThread()){
throw new IllegalMonitorStateException();
}else{
//如果是,就将count-1 若count变为0 ,则解锁成功
int ct = count.get();
int nextc = ct-1;
count.set(nextc);
//判断count值是否为0
if (nextc == 0){
owner.compareAndSet(Thread.currentThread(), null);
return true;
}else{
return false;
}
}
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public Condition newCondition() {
return null;
}
}
ReentrantReadWriteLock
简介
- ReentrantReadWriteLock的实现,主要包括:读写状态的设计、写锁的获取与释放、读锁的获取与释放以及锁降级
读写状态的设计
- 读写锁同样依赖自定义同步器来实现同步功能,而读写状态就是其同步器的同步状态。回想ReentrantLock中自定义同步器的实现,同步状态表示锁被一个线程重复获取的次数,而读写锁的自定义同步器需要在同步状态(一个整型变量)上维护多个读线程和一个写线程的状态,使得该状态的设计成为读写锁实现的关键。
锁降级的概念
- 锁降级指的是写锁降级成为读锁。如果当前线程拥有写锁,然后将其释放,最后再获取读锁,这种分段完成的过程不能称之为锁降级。锁降级是指把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程。
- 接下来看一个锁降级的示例。因为数据不常变化,所以多个线程可以并发地进行数据处理,当数据变更后,如果当前线程感知到数据变化,则进行数据的准备工作,同时其他处理线程被阻塞,直到当前线程完成数据的准备工作,如下代码所示。
public void MartionData() {
readLock.lock();
if (!update) {
// 必须先释放读锁
readLock.unlock();
// 锁降级从写锁获取到开始
writeLock.lock();
try {
if (!update) {
// 准备数据的流程(略)
update = true;
}
readLock.lock();
} finally {
writeLock.unlock();
}
// 锁降级完成,写锁降级为读锁
}
try {
// 使用数据的流程(略)
} finally {
readLock.unlock();
}
}
手写实现ReentrantReadWriteLock
/**
* Martion
* implements ReentrantReadWriteLock (1.1 edition)
*/
public class MartionReadWriteLock {
private AtomicInteger rCount = new AtomicInteger(0);
private AtomicInteger wCount = new AtomicInteger(0);
private AtomicReference<Thread> owner = new AtomicReference<>();
private LinkedBlockingQueue<Node> waiters = new LinkedBlockingQueue<>();
class Node {
int type ;//0:获取写锁的标志 1:获取读锁(共享锁)的标志
Thread thread;//当前线程
int arg;//修改线程的内部次数
public Node(Thread thread,int type,int arg){
this.thread = thread;
this.type = type;// 0 写入 1 读取
this.arg = arg;
}
}
/**
*
* @param accquire
* @return true 获取锁成功 false 获取锁失败
*/
//写锁实现
public boolean tryAccquired(int accquire){
if (rCount.get()!=0)
return false;
int wsCount = wCount.get();
if (wsCount==0){
if (wCount.compareAndSet(wsCount,wsCount+accquire))
owner.set(Thread.currentThread());
return true;
}else if (owner.get() == Thread.currentThread()){
wCount.compareAndSet(wsCount,wsCount+accquire);
return true;
}
return false;
}
/**
* 获取写锁
*/
public void lock(){
int arg = 1;
if (!tryAccquired(arg)){
Node node = new Node(Thread.currentThread(), 0, arg);
waiters.offer(node);
for (;;){
Node head = waiters.peek();
if (head!=null&&head.thread == Thread.currentThread()){
if (!tryAccquired(arg)){
LockSupport.park();
}else{
waiters.poll();
return;
}
}else {
LockSupport.park();
}
}
}
}
/**
* 尝试释放写锁
* @param release true 尝试释放锁成功 false 尝试释放锁失败
* @return
*/
public boolean tryUnlock(int release){
if (owner.get()!=Thread.currentThread())
return false;
int count = rCount.get();
int cureent = count - release;
rCount.set(cureent);//无论是否完全释放 都先设置count值
if (cureent == 0){
owner.compareAndSet(Thread.currentThread(),null);
return true;
}
return false;
}
/**
* 释放写锁
* @return true 释放锁成功 false 释放锁失败
*/
public boolean unLock(){
int arg = 1 ;
if (tryUnlock(arg)){
Node peek = waiters.peek();
if (peek!=null){
Thread thread = peek.thread;
LockSupport.unpark(thread);
}
return true;
}
return false;
}
/**
* 尝试释放读锁 1:获取读锁成功 -1:获取读锁失败
*/
//读锁实现(共享锁)
public int tryLockShard(int accqure){
for (;;){
if (wCount.get()!=0&&owner.get()!=Thread.currentThread()){
return -1;
}
if (rCount.compareAndSet(rCount.get(),rCount.get()+accqure)){
return 1;
}
}
}
/**
* 释放读锁
*/
public void lockShard(){
int arg = 1;
if (tryLockShard(arg)<0){
Node node = new Node(Thread.currentThread(), 1, arg);
waiters.offer(node);
for (;;){
Node heads = waiters.peek();
if (heads!=null&&owner.get()==Thread.currentThread()){
if (tryLockShard(arg)>=0){
Node poll = waiters.poll();
//判断下一级列表是否还为读操作
Node next = waiters.peek();
if (next!=null&&next.type == 1){
LockSupport.unpark(next.thread);
}
return;
}else {
LockSupport.park();
}
}else {
LockSupport.park();
}
}
}
}
/**
* 尝试释放读锁
* @param release
* @return true 尝试释放锁成功 false 尝试释放锁失败
*/
public boolean tryUnLockShard(int release){
for (;;){
int rc = rCount.get();
int current = rc-release;
if (rCount.compareAndSet(rc,current))
return current==0;
}
}
/**
*释放读锁
* @return true 释放锁成功 false 释放锁失败
*/
public boolean unLockShard(){
int arg = 1;
if (tryUnLockShard(arg)){
Node peek = waiters.peek();
if (peek!=null){
LockSupport.unpark(peek.thread);
}
return true;
}
return false;
}
}