Java并发编程:自己动手写一把可重入锁

我们尝试来用Lock显式加锁来解决线程安全的问题,先来看一下Lock接口的定义:

Lock接口有几个重要的方法:

lock()和unlock()是Lock接口的两个重要方法,下面的案例将会使用到它俩。Lock是一个接口,实现它的子类包括:可重入锁:ReentrantLock, 读写锁中的只读锁:ReentrantReadWriteLock.ReadLock和读写锁中的只写锁:

ReentrantReadWriteLock.WriteLock 。我们先来用一用ReentrantLock可重入锁来解决线程安全问题。

控制台输出:

程序中创建了一把锁,一个公共变量的资源,和5个线程,每起一个线程就会对公共资源number做自减操作,从上面的输出可以看到程序中的5个线程对number的操作得到正确的结果。需要注意的是,在你加锁的代码块的finaly语句一定要释放锁,就是调用一下lock的unlock()方法。

现在来看一下什么是可重入锁 ,可重入锁就是同一个线程多次尝试进入同步代码块的时候,能够顺利的进去并执行。实例代码如下:

上述代码什么意思呢?意思是每起一个线程的时候,线程运行run方法的时候,需要去调用sayHello()方法,那个sayHello()也是一个需要同步的和保证安全的方法,方法的第一行代码一来就给方法上锁,然后做完自己的工作之后再释放锁,工作期间,禁止其他线程进来,除了本线程除外。上面代码输出:

实现一把简单的锁

如果你明白了上面几个例子是用来干嘛的,好,我们可以继续进行下去了,我们来实现一把最简单的锁。先不考虑这把锁的公平性和可重入性,只要求达到当使用这把锁的时候我们的代码快安全即可。

我们先来定义自己的一把锁MyLock。

定义自己的锁需要实现Lock接口,而上面是Lock接口需要实现的方法,我们抛开其他因素,只看lock()和unlock()方法。

从上面代码可以看到,这把锁还是照样用到了同步语句synchronized,只是同步的过程我们自己来实现,用户只需要调用我们的锁上锁和释放锁就行了。其核心思想是用一个公共变量isLocked来标志当前锁是否被占用,如果被占用则当前线程等待,然后每被唤醒一次就尝试去抢那把锁一次(处于等待状态的线程不止当前线程一个),这是lock方法里面使用那个while循环的原因。当线程释放锁时,首先将isLocked变量置为false,表示锁没有被占用,其实线程可以使用了,并调用notifyAll()方法唤醒正在等待的线程,至于谁抢到我不管,不是本宝宝份内的事。

那么上面我们实现的锁是不是一把可重入的锁呢?我们来调用sayHello()方法看看:

为了特意演示效果,我在sayHello方法加锁之前打印一下当前线程的名称,现在控制台输出如下:

如上所述,t1线程启动并对公共变量做自减的时候,调用了sayHello方法。同一个线程t1,在线程启动的时候获得过一次锁,再在调用sayHello也想要获取这把锁,这样的需求我们是可以理解的,毕竟sayHello方法也时候也需要达到线程安全效果嘛。可问题是痛一个线程尝试获取锁两次,程序就被卡住了,t1在run方法的时候获得过锁,在sayHello方法想再次获得锁的时候被告诉说:唉,哥们,该锁被使用了,至于谁在使用我不管(虽然正在使用该锁线程就是我自己),你还是等等吧!所以导致结果就是sayHello处于等待状态,而run方法则等待sayHello执行完。控制台则一直处于运行状态。

如果你不理解什么是可重入锁和不可重入锁,对比一下上面使用MyLock的例子和使用J.U.C.包下的ReentrantLock俩例子的区别,ReentrantLock是可重入的,而MyLock是不可重入的。

实现一把可重入锁

现在我们来改装一下这把锁,让他变成可重入锁,也就是说:如果我已经获得了该锁并且还没释放,我想再进来几次都行。核心思路是:用一个线程标记变量记录当前正在执行的线程,如果当前想尝试获得锁的线程等于正在执行的线程,则获取锁成功。此外还需要用一个计数器来记录一下本线程进来过多少次,因为如果同步方法调用unlock()时,我不一定就要释放锁,只有本线程的所有加锁方法都释放锁的时候我才真正的释放锁,计数器就起到这个功能。

改装过后的代码如下:

如代码注释所述,这里新增了两个变量runningThread和count,用于记录当前正在执行的线程和当前线程获得锁的次数。代码的关键点在于while循环判断测试获得锁的线程的条件,之前是只要锁被占用就让进来的线程等待,现在的做法是,如果锁已经被占用,则判断一下正在占用这把锁的就是我自己,如果是,则获得锁,计数器+1;如果不是,则新进来的线程进入等待。相应的,当线程调用unlock()释放锁的时候,并不是立马就释放该锁,而是判断当前线程还有没有其他方法还在占用锁,如果有,除了让计数器减1之外什么事都别干,让最后一个释放锁的方法来做最后的清除工作,当计数器归零时,才表示真正的释放锁。

我知道你在怀疑这把被改造过后的锁是不是能满足我们的需求,现在就让我们来运行一下程序,控制台输出如下:

嗯,没错,这就是我们想要的结果。

好了,自己动手写一把可重入锁就先写到这了。

原文:https://blog.csdn.net

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181116B0C67U00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 yunjia_community@tencent.com 删除。

扫码关注云+社区

领取腾讯云代金券