摘要:通过关键字可以实现多线程之间的同步控制,除了上述方法,为我们提供了很多并发控制的工具类,今天主要讲的就是中的重入锁,效果基本等同于关键字。
在讲重入锁之前,我们先看一段代码
上述代码想要实现的效果,就是使用两个线程对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两个线程会交替获得锁,程序运行效果图如下图:
虽然公平的重入锁可以避免死锁的现象,但因内部必须要维护一个有序的线程队列,所以公平锁的实现成本较高,性能相对低下。
最后附上上述实例代码的地址:https://github.com/Meikoheng/...
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/70859.html
摘要:解决上问题在变量前添加版本号,将变成循环时间长开销大,因为自旋需要消耗只能保证一个共享变量的原子操作分类二重入锁支持重进入的锁,排它锁分类三读写锁一对锁,读锁,写锁,在同一时刻允许多线程访问 1、 分类一:乐观锁与悲观锁 a)悲观锁:认为其他线程会干扰本身线程操作,所以加锁 i.具体表现形式:synchronized关键字和lock实现类 ...
摘要:为什么叫重入锁呢,我们把它拆开来看就明了了。释放锁,每次锁持有者数量递减,直到为止。 相信大家在工作或者面试过程中经常听到重入锁这个概念,或者与关键字 synchrozied 的对比,栈长面试了这么多人,80%的面试者都没有答对或没有答到点上,或者把双重效验锁搞混了,哭笑不得。。 那么你对重入锁了解有多少呢?今天,栈长帮大家撕开重入锁的面纱,来见识下重入锁的真实容颜。。 什么是重入锁 ...
摘要:公平锁非公平锁公平锁公平锁是指多个线程按照申请锁的顺序来获取锁。加锁后,任何其他试图再次加锁的线程会被阻塞,直到当前进程解锁。重量级锁会让其他申请的线程进入阻塞,性能降低。 Java 中15种锁的介绍 在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类。介绍的内容如下: 公平锁 / 非公平锁 可重入锁 / 不可重入锁 独享锁 / 共享锁 互斥锁 / 读...
摘要:很吧判断是否有前驱线程等待获取锁公平所和非公平锁的各自优势是什么那公平锁很好理解,可以防止出现线程饥饿现象,每一个线程都有机会获取到锁。非公平锁可能会导致线程饥饿,但是我们一般使用非公平锁,因为非公平锁可以减少上下文的切换,提高效率。 锁的重入是指同一个线程可以多次获取同一个锁,synchronize是隐式的可重入锁,ReentrantLock通过代码实现了锁的重入: fina...
摘要:所谓的重入,就是当本线程想再次获得锁,不需要重新申请,它本身就已经锁了,即重入该锁。如果不为,则表示有线程已经占有了。总结回顾下要点是一个可重入的锁被当前占用的线程重入。 上一章《AQS源码阅读》讲了AQS框架,这次讲讲它的应用类(注意不是子类实现,待会细讲)。ReentrantLock,顾名思义重入锁,但什么是重入,这个锁到底是怎样的,我们来看看类的注解说明showImg(http:...
阅读 858·2021-09-03 10:42
阅读 1466·2019-08-30 15:56
阅读 1379·2019-08-29 17:27
阅读 843·2019-08-29 15:25
阅读 3092·2019-08-26 18:27
阅读 2442·2019-08-26 13:41
阅读 1849·2019-08-26 10:39
阅读 1416·2019-08-23 18:36