我们尝试来用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
领取专属 10元无门槛券
私享最新 技术干货