ReentrantLock理解及源码分析

写到崩溃!!!我的泪水黄河都装不下了!!!!!!

T__T 不能口吐芬芳

csdn说脏字 审核都不通过 气死我了

ReentrantLock存在的意义

在这里插入图片描述
在这里插入图片描述

ReentrantLock与Synchronized的区别

底层实现

  1. synchronized 是JVM层面的锁,是Java关键字,重量级锁通过monitor对象来完成(monitorenter与monitorexit),对象只有在同步块或同步方法中才能调用wait/notify方法。synchronized 的实现涉及到锁的升级,具体为无锁、偏向锁、自旋锁、向OS申请重量级锁。
    sychronized:访问锁时分别是去CAS对象头的线程ID,对象头指向锁记录的指针,以及Monitor里面的owner等(对于重量锁涉及到哪些os的,我真不知道!!啊啊啊,毕竟看不太懂源码,也不想看)。
  2. ReentrantLock实现则是代码层面保证线程操作的原子性和volatile保证数据可见性。(比如AQS的state用volatile修饰)。
    ReentrantLock:访问锁时通过访问链表第一个节点的线程判断是否重入以及去CAS AQS的state来tryAccquire.

是否可手动释放锁

  1. synchronized 不需要用户去手动释放锁,synchronized 代码执行完后系统会自动让线程释放对锁的占用;
  2. ReentrantLock则需要用户去手动释放锁,如果没有手动释放锁,就可能导致死锁现象。一般通过lock()和unlock()方法配合try/finally语句块来完成,使用释放更加灵活,一个灵活的加锁机制提供了更好的活跃度和性能,也带了了危险。

是否可中断等待锁的线程,否则请求锁失败必须无限等待

  1.  Synchronized对于中断的处理。
     synchronized关键字:线程在等待锁的时候被中断的话不会去处理它
     isInterrupted=true,假如进入同步块里面,有sleep()的话会抛出睡眠时被中断异常。
     假如不存在可以抛出InterruptedException异常的情况,可能就不会被发现。
     与ReentrantLock的.lock()方法处理中断机制一样
    
  2. ReentrantLock的.lockInterruptibly()对于中断的处理。
    lockInterruptibly 等待锁时响应中断,抛出异常->lockInterruptibly()函数抛出异常,则不排队了或者上锁失败了。
    
synchronized等待锁时不响应中断,只改标识位
package demo.test;

import static java.lang.Thread.sleep;

public class Test {
    public synchronized void cal() throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"进来了");
        System.out.println(Thread.currentThread().isInterrupted());
        //System.out.println(Thread.currentThread().interrupted());
        //注释掉上上一行,不注释上一行这样会清除标识位,导致下面睡眠的时候并不会被中断.被不被中断是看中断标识的。
        
        //synchronized关键字:线程在等待锁的时候被中断的话不会去处理它
        //isInterrupted=true,加入进入同步块里面,有sleep()的话会抛出睡眠时被中断异常。
        //加入不存在可以抛出InterruptedException异常的情况,可能就不会被发现。
        //与ReentrantLock的.lock()方法处理中断机制一样
        try{
            sleep(5000);//会将中断标识位置位false.
        }catch (Exception e){
            System.out.println("啊这");
        }
        System.out.println(Thread.currentThread().getName()+"马上要退出了");
    }
    public static void main(String[] args) throws InterruptedException {
        Test test=new Test();
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    test.cal();
                } catch (InterruptedException e) {
                    //e.printStackTrace();
                }
            }
        });
        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    test.cal();
                } catch (InterruptedException e) {
                    //e.printStackTrace();
                }
            }
        });
        t1.start();sleep(1000);t2.start();//保证t1比t2执行
        sleep(1000);t2.interrupt();//保证interrupt的时候t2正在等待锁的获取
        sleep(7000);//保证t2执行完
        System.out.println(t2.interrupted());
    }
}
/*执行结果:
Thread-0进来了
false
Thread-0马上要退出了
Thread-1进来了
true
啊这
Thread-1马上要退出了
false*/

lockInterruptibly 等待锁时响应中断,抛出异常->lockInterruptibly()函数抛出异常->上锁失败or不排队
//采用ReentrantLock 竞争锁时可响应中断而不是继续等待:被中断后并不是将中断标识改为ture而是返回这个中断异常.
//竞争锁park的时候遇到中断:fairLock.lock()采用的方法是 将中断标识改为true(
//因为parkAndCheckInterrupt()里面return Thread.interrupted();这个过程将标识改为了false.),
//继续for循环,来tryAcquire或者park直到获取锁,才返回。。
public void lockInterruptibly() throws InterruptedException {
	sync.acquireInterruptibly(1);
}
//AQS
public final void acquireInterruptibly(int arg)
		throws InterruptedException {//将异常抛出
	if (Thread.interrupted())//这里判断一下有没有被中断
		throw new InterruptedException();
	if (!tryAcquire(arg))
		doAcquireInterruptibly(arg);
}
//AQS
private void doAcquireInterruptibly(int arg)
	throws InterruptedException {
	final Node node = addWaiter(Node.EXCLUSIVE);
	boolean failed = true;
	try {
		for (;;) {
			final Node p = node.predecessor();
			if (p == head && tryAcquire(arg)) {
				setHead(node);
				p.next = null; // help GC
				failed = false;
				return;
			}
			if (shouldParkAfterFailedAcquire(p, node) &&
				parkAndCheckInterrupt())//parkAndCheckInterrupt()这个函数与之前的一样
				throw new InterruptedException();
			//区别就是在这里,被中断后并不是将中断标识改为ture而是返回这个中断异常.
		}
	} finally {
		if (failed)
			cancelAcquire(node);
	}
}

锁是否可以绑定条件(挖坑 Condition还没看,也没用过)

tryLock(notfairSync的nonfairTryAcquire方法)可以解决一些用synchronized解决很繁琐的死锁问题

在这里插入图片描述

  1. 原问题:
    在这里插入图片描述
  2. 原来解决方案:
    在这里插入图片描述
    在这里插入图片描述
  3. 使用tryLock解决:
    在这里插入图片描述

非块结构的锁(粒度更细) (挖坑!!!啊啊啊啊啊啊让我死了算了)

公平锁的上锁

acquire(int arg) 【加锁】

//公平锁的上锁
ReentrantLock fairLock=new ReentrantLock();
fairLock.lock();
//FairSync
final void lock(){
	acquire(1);
}
//AQS 尝试获取不到锁的话就创建节点,加入队列
public final void acquire(int arg){		
	if (!tryAcquire(arg) &&
		acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//tryAcquire不成功的话addWaiter(Node.EXCLUSIVE)->acquireQueued();
		//acquireQueued()直到获得锁,才能继续往下执行(可以看acquireQueued()函数),解决了互斥问题.
		selfInterrupt();//没被中断的话,acquireQueued()返回false,不会执行这句话
	}
}

tryAcquire(int acquires) 【尝试加锁】

//FairSync
//队列为空(head==tail==null)或者队列不存在(其实head=tail==null就是队列不存在了)
//或者重入才可以tryAcquire成功
protected final boolean tryAcquire(int acquires) {
	final Thread current = Thread.currentThread();
	int c = getState();
	if (c == 0) {//不被任何线程占用
		if (!hasQueuedPredecessors() &&
			compareAndSetState(0, acquires)) {//队列为空(head=tail=null)或head.next==null(即队列只有Head) 尝试CAS操作
			setExclusiveOwnerThread(current);//将exclusiveOwnerThread设置为当前线程
			return true;//tryAcquire成功
		}
	}
	else if (current == getExclusiveOwnerThread()) {//重入的时候也可以成功
		int nextc = c + acquires;
		if (nextc < 0)
			throw new Error("Maximum lock count exceeded");
		setState(nextc);
		return true;
	}
	return false;
}

hasQueuedPredecessors() 【队是否为空or是否重入】

//AQS
//当队列为空 或者 队里第一个线程就是当前线程的时候 返回false.
public final boolean hasQueuedPredecessors() {
	Node t = tail;
	Node h = head;
	Node s;
	return h != t &&
		((s = h.next) == null || s.thread != Thread.currentThread());
}

acquireQueued(final Node node,int arg) 【排队(tryAcquire()/prak)】

//AQS
//这是一个死循环,直到p是队里第一个节点而且tryAcquire成功后return了,
//才能退出这个函数,否则->acquire()不能退出->sync.lock()不能退出.
final boolean acquireQueued(final Node node, int arg) {
	boolean failed = true;
	try {
		boolean interrupted = false;
		for (;;) {//死循环
			final Node p = node.predecessor();
			if (p == head && tryAcquire(arg)) {
				//假如前一个是head节点的话,先尝试下CAS能不能获取到锁(getState=0时),
				//不能的话就先park等待被唤醒后继续for(;;)里tryAcquire()
				setHead(node);//成功之后,将head改为指向node的引用
				p.next = null; //p.next也就是node.predecessor().next也就是node改为null(1.因为head所以要是null)
				//2.p.next=null的时候,p所指的对象都为null了(也就是原来的head,这样就会被当作垃圾被回收)
				// help GC 
				failed = false;
				return interrupted;
			}//tryAcquire不成功的话且前一个ws=SIGNAL(也就是前面还没解锁),
			//就会进入睡眠,直到前面解锁并唤醒它之后再次for进入tryAcquire
			if (shouldParkAfterFailedAcquire(p, node) &&
				parkAndCheckInterrupt())//假如应该park的话就进入parkAndCheckInterrupt();
				interrupted = true;//这里是因为 下面parkAndCheckInterrupt()方法return了Thread.interrupted();有中断的话会将标识位清除.
		}
	} finally {
		if (failed)
			cancelAcquire(node);
	}
}

shouldParkAfterFailedAcquire(Node pred,Node node)/parkAndCheckInterrupt() 【是否应该自旋】

//AQS
//判断应不应该tryAcquire失败之后park.
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
	int ws = pred.waitStatus;
	if (ws == Node.SIGNAL)
		return true;//前一个示意后面都要park了。。 所以直接park。
	if (ws > 0) {
		do {
			node.prev = pred = pred.prev;
		} while (pred.waitStatus > 0);
		pred.next = node;
	} else {//---------
		compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
		//一般情况下这里应用于 初始的时候wait.Status都是0要改为-1
		//将前一个节点的状态设置为Node.SIGNAL.
	}
	return false;
}
//AQS
private final boolean parkAndCheckInterrupt() {
	LockSupport.park(this);
	return Thread.interrupted();
}

addWaiter(Node mode)/enq(final Node node) 【添加节点到队列中】

//AQS
//创建一个新的节点放在队尾,作为tail并返回该节点
private Node addWaiter(Node mode) {
	Node node = new Node(Thread.currentThread(), mode);
	Node pred = tail;
	if (pred != null) {
		node.prev = pred;
		if (compareAndSetTail(pred, node)) {
			pred.next = node;
			return node;
		}
	}
	enq(node);
	return node;
}
//AQS
//创建head,和一个新的节点t,head.next=t,这个节点t作为tail,return t;
private Node enq(final Node node) {
	for (;;) {
		Node t = tail;
		if (t == null) { // Must initialize
			if (compareAndSetHead(new Node()))
				tail = head;
		} else {
			node.prev = t;
			if (compareAndSetTail(t, node)) {
				t.next = node;
				return t;
			}
		}
	}
}

总结上锁过程(挖坑 一总结就头疼)

公平锁的解锁

release(int arg) 【解锁】

//公平锁的解锁
fairLock.unlock();
public void unlock() {
	sync.release(1);
}
//AQS
//release之后,还在被占用(也就是重入的情况),返回false.
//release之后,不被占用了,unpark了下一个线程,返回true.
public final boolean release(int arg) {
	if (tryRelease(arg)) {
		Node h = head;
		if (h != null && h.waitStatus != 0)
		//h.waitStatus!=0代表head后面有人排队.
		//因为有人排队的话,会在加锁里面shouldParkAfterFailedAcquire()里面将前一个结点改为-1
			unparkSuccessor(h);//唤醒下一个线程
		return true;
	}
	//没释放锁成功(这个意思并不是操作失败了,
	//而是重入的时候释放次数<加锁次数的意思,还被锁着的意思) 返回false.
	return false;
}

trylease(int releases) 【尝试解锁,解到0就算成功】

//Sync
//假如是重入的话,解锁次数<小于加锁次数会tryRelease返回false.
//不是重入的话,c等于0,返回true.
protected final boolean tryRelease(int releases) {
	int c = getState() - releases;
	if (Thread.currentThread() != getExclusiveOwnerThread())
		throw new IllegalMonitorStateException();//上锁线程和解锁线程不是同一个则抛出异常
	boolean free = false;
	if (c == 0) {//锁没有被线程占用
		free = true;
		setExclusiveOwnerThread(null);
	}
	setState(c);
	return free;
}

unparkSuccessor(Node node) 【将node下一节点线程唤醒】

//AQS
private void unparkSuccessor(Node node) {
	int ws = node.waitStatus;
	if (ws < 0)//这里改为0我猜是好习惯吧。
		compareAndSetWaitStatus(node, ws, 0);
	Node s = node.next;
	if (s == null || s.waitStatus > 0) {//这个条件我也不知道为什么,有很多原因是我们不能知道的。比如线程被别的介入了?
		s = null;
		for (Node t = tail; t != null && t != node; t = t.prev)
			if (t.waitStatus <= 0)
				s = t;
	}
	if (s != null)
		LockSupport.unpark(s.thread);//unpark下一个线程,就是s.
}

总结解锁过程(挖坑)

非公平锁

说一点比较重要的:

unparkSuccessor()先compareAndSetWaitStatus(node, ws, 0);然后LockSupport.unpark(s.thread);unpark下一个线程。 不公平锁可能在CAS-unpark之间上了锁,那第一个节点就被唤醒(其实唤醒是没用的),但是这样代码比较好实现吧,否则我也想不到该咋办(毕竟我这么菜),正好acquireQueued(),被唤醒之后也要继续for(;;)来tryAcquire来能拿到锁,拿不到的时候再继续park。这个实现挺好的,,

源码

//不公平的解锁与公平锁的解锁步骤相同
//不公平锁的上锁
ReentrantLock unfairLock=new ReentrantLock();
unfairLock.lock();
//NotFairSync
final void lock() {
	//不需要等到acquire里面的tryAcquire()才CAS,这个时候也CAS一下
	//(差不多吧,先CAS一下,失败了还能再acquire的tryAcquire里CAS一下)
	if (compareAndSetState(0, 1))
		setExclusiveOwnerThread(Thread.currentThread());
	else
		acquire(1);
}
//AQS
public final void acquire(int arg){		
	if (!tryAcquire(arg) &&
		acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
		selfInterrupt();
	}
}
//NotFairSync
protected final boolean tryAcquire(int acquires) {
	return nonfairTryAcquire(acquires);
}
//Sync
final boolean nonfairTryAcquire(int acquires) {
	final Thread current = Thread.currentThread();
	int c = getState();
	if (c == 0) {
		//不需要 队列为空或者重入 的时候才能尝试CAS,不管为不为空直接CAS,所以才说是不公平锁
		if (compareAndSetState(0, acquires)) {
			setExclusiveOwnerThread(current);
			return true;
		}
	}
	else if (current == getExclusiveOwnerThread()) {
		int nextc = c + acquires;
		if (nextc < 0) // overflow
			throw new Error("Maximum lock count exceeded");
		setState(nextc);
		return true;
	}
	return false;
}

与公平锁性能对比

在这里插入图片描述
我觉着这里是因为正在占用的线程释放锁后到第一个节点得到锁会有延迟:占用的将state改为0 -> unpark下一个线程(也就是第一个节点) -> 第一个线程tryAcquire来一遍CAS(改state)也setExclusiveOwnerThread(current),才会有延迟的。

应用场景(烦死了!不想写!这玩意儿咋这么多 好累挖坑)

🔗

1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看REAdMe.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看REAdMe.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。 1、资源项目源码均已通过严格测试验证,保证能够正常运行; 2、项目问题、技术讨论,可以给博主私信或留言,博主看到后会第一时间与您进行沟通; 3、本项目比较适合计算机领域相关的毕业设计课题、课程作业等使用,尤其对于人工智能、计算机科学与技术等相关专业,更为适合; 4、下载使用后,可先查看READme.md或论文文件(如有),本项目仅用作交流学习参考,请切勿用于商业用途。 5、资源来自互联网采集,如有侵权,私聊博主删除。 6、可私信博主看论文后选择购买源代码。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值