温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Java中ReentrantLock是如何实现以及源码是怎样的

发布时间:2021-09-24 09:20:58 来源:亿速云 阅读:162 作者:柒染 栏目:开发技术

Java中ReentrantLock是如何实现以及源码是怎样的,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。

一、ReentrantLock简介

ReentrantLock位于Java的juc包里面,从JDK1.5开始出现,是基于AQS同步队列的独占模式实现的一种锁。ReentrantLock使用起来比synchronized更加灵活,可以自己控制加锁、解锁的逻辑。ReentrantLock跟synchronized一样也是可重入的锁,提供了公平/非公平两种模式:

  1. 公平锁:多个线程竞争锁的时候,会先判断等待队列中是否有等待的线程节点,如果有则当前线程会进行排队,锁的获取顺序符合请求的绝对时间顺序,也就是 FIFO

  2. 非公平锁:当前线程竞争锁的时候不管有没有其他线程节点在排队,都会先通过CAS尝试获取锁,获取失败了才会进行排队。

通过new ReentrantLock()的方式创建的是非公平锁,要想创建公平锁需要在构造方法中指定new ReentrantLock(true)。ReentrantLock的常用方法如下:

  • void lock() 获取锁,如果当前线程获取锁成功将返回,获取锁失败线程将被阻塞、挂起

  • void lockInterruptibly() throws InterruptedException 可中断的获取锁,和lock方法的不同之处在于该方法会响应中断,即在锁的获取过程中可以中断当前线程

  • boolean tryLock() 尝试非阻塞的获取锁,方法会立即返回,获取锁成功返回true,否则返回false

  • boolean tryLock(long time, TimeUnit unit) throws InterruptedException 尝试在指定超时时间内获取锁,如果当前线程获取了锁会立即返回true,如果被其他线程获取了锁则会被阻塞挂起,该方法会在下面三种情况下返回:1,在超时时间内获取了锁,返回true;2,在超时时间内线程被中断;3,超时时间结束,返回false。

  • void unlock() 释放锁

  • Condition newCondition() 获取等待通知组件,该组件与当前的锁绑定,当前线程只有获取了锁,才能调用Condition的wait()方法,调用wait()方法后会释放锁

二、ReentrantLock使用

ReentrantLock的使用方式一般如下,一定要在finally里面进行解锁,防止程序出现异常无法解锁

ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
	System.out.println("获取了锁");
} catch (Exception e) {
	e.printStackTrace();
} finally {
	lock.unlock();
}

下面通过一个程序示例,演示一下ReentrantLock的使用:对同一个lock对象做多次加锁,解锁,演示一下ReentrantLock的锁重入

public class ReentrantLockTest {
    private Integer counter = 0;
    private ReentrantLock lock = new ReentrantLock();
    
    public void modifyResources(String threadName){
        System.out.println("线程:--->"+threadName+"等待获取锁");
        lock.lock();
        System.out.println("线程:--->"+threadName+"第一次加锁");
        counter++;
        System.out.println("线程:"+threadName+"做第"+counter+"件事");
        //重入该锁,我还有一件事情要做,没做完之前不能把锁资源让出去
        lock.lock();
        System.out.println("线程:--->"+threadName+"第二次加锁");
        counter++;
        System.out.println("线程:"+threadName+"做第"+counter+"件事");
        lock.unlock();
        System.out.println("线程:"+threadName+"释放一个锁");
        lock.unlock();
        System.out.println("线程:"+threadName+"释放一个锁");
    }
 
    public static void main(String[] args) throws InterruptedException {
        ReentrantLockTest tp = new ReentrantLockTest();
 
        new Thread(()->{
            String threadName = Thread.currentThread().getName();
            tp.modifyResources(threadName);
        },"Thread:张三").start();
 
        new Thread(()->{
            String threadName = Thread.currentThread().getName();
            tp.modifyResources(threadName);
        },"Thread:李四").start();
 
        Thread.sleep(100);
    }
}

程序运行输出如下所示:上面代码中lock加锁两次然后解锁两次,在张三线程两次解锁完成之前,李四线程一直在等待。ReentrantLock加锁了几次,就要解锁相同的次数才可以释放锁。

线程:--->Thread:张三等待获取锁
线程:--->Thread:张三第一次加锁
线程:Thread:张三做第1件事
线程:--->Thread:张三第二次加锁
线程:--->Thread:李四等待获取锁
线程:Thread:张三做第2件事
线程:Thread:张三释放一个锁
线程:Thread:张三释放一个锁
线程:--->Thread:李四第一次加锁
线程:Thread:李四做第3件事
线程:--->Thread:李四第二次加锁
线程:Thread:李四做第4件事
线程:Thread:李四释放一个锁
线程:Thread:李四释放一个锁

三、ReentrantLock源码分析

ReentrantLock实现了Lock接口,它有一个内部类Sync实现了前面介绍过的AbstractQueuedSynchronizer,而其公平锁、非公平锁分别通过Sync的子类FairSync、NonFairSync(也是ReentrantLock的内部类)实现。下面看下其UML图

Java中ReentrantLock是如何实现以及源码是怎样的

lock()方法调用时序图如下:

Java中ReentrantLock是如何实现以及源码是怎样的

前面《Java并发编程之JUC并发核心AQS同步队列原理剖析》介绍AQS的时候说过,AbstractQueuedSynchronizer中有一个状态变量state,在ReentrantLock中state等于0表示没有线程获取锁,如果等于1说明有线程获取了锁,如果大于1说明获取锁的线程加锁的次数,加了几次锁就必须解锁几次,每次unlock解锁state都会减1,减到0时释放锁。

1、非公平锁源码分析

前面一篇博客《Java并发编程之JUC并发核心AQS同步队列原理剖析》对AQS介绍的已经非常详细了,所以下面源码分析中牵涉AQS中的方法就不再进行介绍了,想了解的话可以看下那篇博客。

先看下非公平锁的加锁lock方法,lock方法中调用了sync的lock方法,而sync对象时根据构造ReentrantLock时是公平锁(FairSync)还是非公平锁(NonFairSync)。

public void lock() {
	sync.lock();
}

这里调用的是非公平锁,所以我们看下 NonFairSync的lock方法:进来时不管有没有其他线程持有锁或者等待锁,会先调用AQS中的compareAndSetState方法尝试获取锁,如果获取失败,会调用AQS中的acquire方法

final void lock() {
	/**
	 * 第一步:直接尝试加锁
	 * 与公平锁实现的加锁行为一个最大的区别在于,此处不会去判断同步队列(CLH队列)中
	 * 是否有排队等待加锁的节点,上来直接加锁(判断state是否为0,CAS修改state为1)
	 * ,并将独占锁持有者 exclusiveOwnerThread 属性指向当前线程
	 * 如果当前有人占用锁,再尝试去加一次锁
	 */
	if (compareAndSetState(0, 1))
		setExclusiveOwnerThread(Thread.currentThread());
	else
		//AQS定义的方法,加锁
		acquire(1);
}

下面看下acquire方法,会先调用NonFairSync类中重写的tryAcquire方法尝试获取锁,如果获取锁失败会调用AQS中的acquireQueued方法进行排队、阻塞等处理。

public final void acquire(int arg) {
	if (!tryAcquire(arg) &&
			acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
		selfInterrupt();
}

下面看下NonFairSync类中重写的tryAcquire方法,里面又调用了nonfairTryAcquire方法

protected final boolean tryAcquire(int acquires) {
	return nonfairTryAcquire(acquires);
}

下面看下nonfairTryAcquire方法:

  • 判断state如果为0,通过CAS的方式尝试获取锁,如果获取锁成功,则将当前线程设置为独占线程

  • 如果state不为0,则判断当前线程是否跟独占线程时同一个线程,如果是同一个线程则将锁的state加1,也就是锁的重入次数加1

  • 否则获取锁失败,返回false

final boolean nonfairTryAcquire(int acquires) {
	//acquires = 1
	final Thread current = Thread.currentThread();
	int c = getState();
	/**
	 * 不需要判断同步队列(CLH)中是否有排队等待线程
	 * 判断state状态是否为0,不为0可以加锁
	 */
	if (c == 0) {
		//unsafe操作,cas修改state状态
		if (compareAndSetState(0, acquires)) {
			//独占状态锁持有者指向当前线程
			setExclusiveOwnerThread(current);
			return true;
		}
	}
	/**
	 * state状态不为0,判断锁持有者是否是当前线程,
	 * 如果是当前线程持有 则state+1
	 */
	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;
}

下面看下非公平锁的解锁过程:unlock方法中调用了AQS中的release方法

public void unlock() {
	sync.release(1);
}

AQS中的release方法如下所示:会先调用AQS的子类Sync中重写的tryRelease方法去释放锁,如果是否锁成功,则唤醒同步队列中head的后续节点,后续节点线程被唤醒会去竞争锁。

public final boolean release(int arg) {
	if (tryRelease(arg)) {//释放一次锁
		Node h = head;
		if (h != null && h.waitStatus != 0)
			unparkSuccessor(h);//唤醒后继结点
		return true;
	}
	return false;
}

Sync中重写的tryRelease方法:

获取当前的state值,然后减1

判断当前线程是否是锁的持有线程,如果不是会抛出异常。

如果state的值被减到了0,表示锁已经被释放,会将独占线程设置为空null,将state设置为0,返回true,否则返回false。

/**
 * 释放锁
 */
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;
}

2、公平锁源码分析

先看下公平锁的加锁lock方法,lock方法中调用了sync的lock方法,这里调用的是FairSync的lock方法。

public void lock() {
	sync.lock();
}

FairSync的lock方法直接调用了AQS中的acquire方法,没有像非公平锁先通过CAS的方式先去尝试获取锁

final void lock() {
	acquire(1);
}

下面看下acquire方法,会先调用FairSync类中重写的tryAcquire方法尝试获取锁,如果获取锁失败会调用AQS中的acquireQueued方法进行排队、阻塞等处理。

public final void acquire(int arg) {
	if (!tryAcquire(arg) &&
			acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
		selfInterrupt();
}

下面看下FairSync类中重写的tryAcquire方法,这个方法跟NonFairSync的唯一区别就是state为0的时候,公平锁会先通过hasQueuedPredecessors()方法判断是否队列中是否有等待的节点,如果没有才会尝试通过CAS的方式去获取锁,非公平锁不会判断直接回尝试获取锁。

protected final boolean tryAcquire(int acquires) {
	final Thread current = Thread.currentThread();
	int c = getState();
	if (c == 0) {
		/**
		 * 与非公平锁中的区别,需要先判断队列当中是否有等待的节点
		 * 如果没有则可以尝试CAS获取锁
		 */
		if (!hasQueuedPredecessors() &&
				compareAndSetState(0, acquires)) {
			//独占线程指向当前线程
			setExclusiveOwnerThread(current);
			return true;
		}
	}
	else if (current == getExclusiveOwnerThread()) {
		int nextc = c + acquires;
		if (nextc < 0)
			throw new Error("Maximum lock count exceeded");
		setState(nextc);
		return true;
	}
	return false;
}

公平锁的unlock方法与非公平锁的代码一样,这里就不再介绍了。

看完上述内容是否对您有帮助呢?如果还想对相关知识有进一步的了解或阅读更多相关文章,请关注亿速云行业资讯频道,感谢您对亿速云的支持。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI