Java锁中的重入锁该怎么理解
这篇文章将为大家详细讲解有关Java锁中的重入锁该怎么理解,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。
在讲重入锁之前,我们先看一段代码
上述代码想要实现的效果,就是使用两个线程对i分别进行累加一百万次,最终希望i的值是二百万,如果按照上述代码运行程序,你会发现i的值在绝大多数情况下都不能达到200万,原因就是多线程的数据同步问题。
为了解决上述问题,我们自然而然想到synchronized关键字,通过对程序进行简单改造,如下图,红框中的部分就是程序变动的部分:
在此处synchronized关键字的作用就是,当每个线程试图对i进行++操作时,必须要先获取o对象,一个o对象在同一时刻只能被一个线程所持有,其他线程必须要等待持有o对象的线程进行i++操作并且释放o对象之后去试图获取o对象,如果获取成功线程继续执行,如果获取失败,线程继续等待。通过synchronized关键字会使原本并行化的操作变成顺序执行,也就是说同一时刻,只会有一个线程对i进行++,因此i最终的值必定会是200万。
通过synchronized关键字可以实现多线程之间的同步控制,除了上述方法,Java为我们提供了很多并发控制的工具类,今天主要讲的就是Java中的重入锁ReentrantLock,效果基本等同于synchronized关键字。
使用重入锁必须获取一个重入锁对象,通过new一个ReentrantLock即可获得一个重入锁对象。
使用重入锁必须明确指定加锁和解锁操作,增强程序的可读性。
同一把重入锁只能在同一时刻只能被同一个线程锁持有,也就是说,当线程1通过lock方法获取锁成功之后,其他线程如果想要获得锁必须等待线程1通过unlock方法释放锁之后才能获取成功。
重入锁支持多次加锁和多次解锁操作,但是加锁和解锁的次数必须保持一致,如果一个线程的加锁次数大于解锁次数,会使得当前线程一直占有这把重入锁,其他线程永远无法获取锁,从而产生饥饿现象,相反如果解锁的次数大于加锁次数,程序则会抛出IllegalMonitorStateException异常。
重入锁提供中断响应,就是在等待锁的过程中可以取消对锁的请求。
通过图片上的代码,很轻松的就构造了一个死锁现象,当lock值是1,线程会先试图获取重入锁lock1,500ms之后再试图获取重入锁lock2,相反如果lock值不是1,线程会先试图获取重入锁lock2,500ms之后在试图获取重入锁lock1,此时,我在主函数中新开两个线程,设置lock的值一个为1,另一个为2;
此时运行程序,你会发现程序永远不会结束,原因就是两个线程之间形成了死锁现象。
细心的读者或许已经发现,我在获取重入锁的时候不是使用lock()方法,而是使用的lockInterruptibly()方法,通过方法名称也可以看出,lockInterruptibly()方法是支持中断响应的。
下面我会在主线程通过t2.interrupt()中断thread-2线程,这样重入锁2就会被释放,从而使得thread-1可以正确执行完毕,但是thread-2只是被中断,无法正确执行完毕,只会执行finally块中的方法,最终程序的输出结果如下图:
除了通过中断线程我们还可以通过锁申请等待限时来避免死锁和饥饿现象,所谓的锁申请等待限时指的是申请锁时指定一个最大等待时间,如果超过了等待时间还没获得锁,线程就不再进行等待并且继续执行。
想要实现上述效果只需要在获取锁时使用tryLock方法来获取锁就可以,此方法会有一个boolean返回值,如果获取锁成功,返回值为true,如果失败,返回值即为false。该方法有两个重载方法,如下图:
上述的实现方式都是非公平锁,所谓的非公平就是,线程获取锁的成功率是随机的,有些锁可能会一直成功获取锁,而有些线程会一直获取不到锁,而那些获取不到锁的线程就会一直处于等待状态,从而产生饥饿现象。
为了解决上述问题,重入锁支持多个线程之间以一种公平的方式来竞争获取锁,通俗一点讲比如有两个线程,两个线程试图获取同一把锁,假如说第一次成功获取锁的是线程1,那么下次成功获取锁的必定是线程2而不是线程1。
公平重入锁的实现只需要在获取重入锁时,构造参数中指定true。
上述代码通过主线程中新开两个线程,每个线程所做的事就是循环的获取fairLock这把重入锁,由于fairLock是一把公平的重入锁,因此t1和t2两个线程会交替获得锁,程序运行效果图如下图:
虽然公平的重入锁可以避免死锁的现象,但因内部必须要维护一个有序的线程队列,所以公平锁的实现成本较高,性能相对低下。
关于Java锁中的重入锁该怎么理解就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。