当前位置: 首页 > 后端技术 > Java

Lock锁重入与并发编程公平性系列

时间:2023-04-01 14:37:12 Java

1.相似点:Lock锁vsSynchronized代码块Lock锁是一种类似于synchronized同步代码块的线程同步机制。从Java5开始,java.util.concurrent.locks引入了几个Lock锁实现类,所以通常我们不需要自己实现锁。重要的是知道如何使用它们并理解它们的实现背后的原理。LockAPI的基本用法与Synchronized关键字类似,代码如下Locklock=newReentrantLock();//实例化锁//lock.lock();//锁布尔值locked=lock.tryLock();//尝试加锁if(locked){try{//加锁的同步代码块只能同时被一个线程执行}finally{lock.unlock();//放在finally代码块中,保证锁会被加锁Release}}synchronized(obj){//加锁的synchronized代码块只能同时被一个线程执行}Lock锁好像有点麻烦可以使用,但是java默认提供了很多Lock锁,可以满足更多的应用场景。比如:基于信号量的锁,读写锁等,关注我的专栏《java并发编程》,后面会介绍。2.Lock接口中的方法Lock接口的实现方法通常会维护一个计数器。当计数器=0时,资源被释放,当计数器大于1时,资源被锁定。公共接口锁{voidlock();voidlockInterruptibly()抛出InterruptedException;布尔tryLock();booleantryLock(longtime,TimeUnitunit)throwsInterruptedException;无效解锁();该方法会将锁计数器加1,如果此时共享资源空闲,则将锁给调用该方法的线程。unlock()-调用此方法将锁定计数器减1,当锁定计数器达到0时,资源将被释放。tryLock()-如果资源空闲,调用此方法将返回true,并且锁定计数器将递增1。如果资源正在使用,则该方法返回false,但不会阻塞线程。tryLock(longtimeout,TimeUnitunit)-尝试根据此方法获取锁。如果此时资源被占用,则线程等待一定时间后退出。该时间段由方法的参数定义,以便期望在这段时间内获得资源锁。lockInterruptibly()-如果资源空闲,此方法获取锁,同时允许线程在获取资源时被其他线程中断。这意味着,如果当前线程正在等待锁,但其他线程申请了锁,则当前线程将被中断并立即返回,而不会获取锁。3.区别:Lock锁vsSynchronized代码块使用synchronized同步块和使用LockAPI还是有一些区别的。一个synchronized的同步块必须完全包含在一个方法中——但是LockAPI的lock()和unlock()操作,可以在不同的方法中同步synchronized块不支持公平原则,任何线程都可以重新获取锁之后它已发布,无法指定优先级。但是我们可以在LockAPI中通过指定fairness属性实现公平优先级,可以让等待时间最长的线程获得锁的占用权。如果一个线程不能访问同步块,它就会被阻塞等待。LockAPI提供tryLock()方法尝试获取锁对象,获取到锁返回true,否则返回false。返回false不会阻塞线程,所以使用这个方法可以减少线程等待锁的阻塞时间。4、锁可重入性“可重入性”是指一个线程可以安全地多次获取同一个锁对象而不会造成死锁。4.1.synchronized锁的重入下面代码中的synchronized代码块嵌套了synchronized代码块来锁定同一个this对象,不会死锁。证明synchronized代码块锁定同一个对象,是可重入的。publicvoidtestLock(){synchronized(this){System.out.println("第一次获取锁,锁对象为:"+this);整数索引=1;do{synchronized(this){System.out.println("th"+(++index)+"获取锁,锁对象为:"+this);}}while(index!=10);}}上面代码的输出是第一次获取锁,锁对象为:com.example.demo.thread.TestLockReentrant@769c9116第二次获取锁,锁对象为:com.example.demo.thread.TestLockReentrant@769c9116第三次获取锁,锁对象为:com.example.demo.thread.TestLockReentrant@769c9116第四次获取锁,锁对象为:com.example.demo.thread.TestLockReentrant@769c9116第五次获取锁,锁对象为:com.example.demo.thread.TestLockReentrant@769c9116第六次获取锁,锁对象为:com.example。demo.thread.TestLockReentrant@769c9116第七次获取锁,锁对象是:com.example.demo.thread.TestLockReentrant@769c9116第八次获取锁,锁对象是:com.example.demo。thread.TestLockReentrant@769c9116第9次获取锁,锁对象为:com.example.demo.thread.TestLockReentrant@769c9116第10次获取锁,锁对象为:com.example.demo。thread.TestLockReentrant@769c91164.2。ReentrantLock可重入锁锁接口实现类ReentrantLock也是一个可重入锁。一般来说,类名包含Reentrant的Lock接口实现类实现的锁都是可重入的。publicvoidtestLock1(){锁lock=newReentrantLock();//实例化锁lock.lock();//lockSystem.out.println("第一次获取锁,锁对象为:"+lock);尝试{int索引=1;做{锁。锁();//加锁try{System.out.println("第"次"+(++index)+"获取锁,锁对象为:"+lock);}finally{lock.unlock();}}while(index!=10);}finally{lock.unlock();//放入finally代码块,保证会释放锁}}当线程获取到锁后,计数器置1.线程在解锁之前可以再次获取锁,每次计数器加1。每次解锁操作,计数器减1,当计数器减到0时,锁定的资源被释放。所以最重要的问题是:lock(tryLock)应该和unlock方法成对出现,即代码中一旦加了锁,就必须解锁一次,否则会死锁。没有保证优先级顺序threadsaregrantedaccess(possession).因此,如果许多线程不断竞争访问同一个同步块,则一个或多个线程可能永远不会被授予访问权限。这会产生所谓的“线程饥饿”。为了避免这种情况,锁应该是公平的。锁lock=newReentrantLock(true);可重入锁提供了一个fairness参数fairness,通过该参数Lock锁会服从锁请求的先后顺序,即某个线程解锁资源后,等待时间最长的rout会交出锁。这种公平模式是通过在锁的构造函数中传递“true”来设置的。欢迎关注我的博客,本文转载更多优质知识合集,注明出处(一定要有链接,不能只是文字):字母哥博客-zimug.com如果觉得对你有帮助,请点赞分享!您的支持是我创作不竭的动力!.另外,作者近期输出了以下优质内容,期待大家的关注。《kafka修炼之道》《手摸手教你学Spring Boot2.0》《Spring Security-JWT-OAuth2一本通》《实战前后端分离RBAC权限管理系统》《实战SpringCloud微服务从青铜到王者》