前言:
眼前小伙伴们对“lock判断是否获取锁”大约比较珍视,同学们都需要剖析一些“lock判断是否获取锁”的相关文章。那么小编也在网络上收集了一些关于“lock判断是否获取锁””的相关内容,希望咱们能喜欢,我们快快来了解一下吧!难度
初级
学习时间
10分钟
适合人群
零基础
开发语言
Java
开发环境JDK v11IntelliJ IDEA v2018.3友情提示本教学属于系列教学,内容具有连贯性,本章使用到的内容之前教学中都有详细讲解。本章内容针对零基础或基础较差的同学比较友好,可能对于有基础的同学来说很简单,希望大家可以根据自己的实际情况选择继续看完或等待看下一篇文章。谢谢大家的谅解!1.温故知新
前面在《“全栈2019”Java多线程第二十四章:等待唤醒机制详解》一章中介绍了等待唤醒机制。
在《“全栈2019”Java多线程第二十五章:生产者与消费者线程详解》一章中介绍了生产者与消费者线程。
在《“全栈2019”Java多线程第二十六章:同步方法生产者与消费者线程》一章中介绍了用同步方法来实现生产者与消费者线程例子。
现在我们来讲解显式锁Lock获取lock/释放unlock锁。
2.什么是隐式锁?
我们本章标题叫“显式锁Lock”,那什么是显式锁呢?
下一小节回答这个问题。
相反的,有没有隐式锁呢?
有,就是synchronized关键字修饰的代码块和方法(即同步代码块/方法)用的锁。
在《“全栈2019”Java多线程第十七章:同步锁详解》一章中我们了解到每一个Java对象都可以是同步对象,而每一个Java对象都有一个内置锁(监听器),在任何时刻有且仅有一个线程持有这把锁,同步代码块/方法执行完毕释放锁。
先来写一个没有使用同步的例子。
例子很简单,用匿名内部类实现Runnable接口:
接着,在run()方法中输出当前线程名称+随便一句话:
然后,我们让线程每次输出前睡1秒钟:
接着,我们需要让线程无限执行这样的操作:
然后,创建几个线程:
接着,启动线程:
好了,例子书写完毕,运行程序,执行结果:
从运行结果来看,符合预期,三个线程同时执行。
下面,我们将这个例子改为同步。
只需将while循环内部用同步代码块包裹即可:
运行程序,执行结果:
从运行结果来看,符合预期。
但是,大家发现没有,总是“Thread-0”线程在运行,其他线程没有出现过,这是怎么回事呢?
这种现象叫非公平锁,就是好几个线程同时在等待获取锁,却一直被同一个线程拿到,这对其他线程来说不公平。
怎么解决这个现象?
有非公平锁,就有公平锁(即对每个等待获取锁的线程都有机会拿到锁),在下一章会讲解如何解决这种现象。
3.显式锁Lock
上一小节讲了,什么是隐式锁,接下来就来讲解什么是显式锁。
Java将锁也封装成对象,并用接口来描述了一下:
Lock接口里面有6个方法:
void lock();void lockInterruptibly() throws InterruptedException;boolean tryLock();boolean tryLock(long time, TimeUnit unit) throws InterruptedException;void unlock();Condition newCondition();
这在6个方法下面我们会依次讲解到。
显式锁Lock是什么呢?
显式锁Lock提供了比使用同步代码块/方法获得的更广泛的锁操作。
简单来说就是具备和同步代码块/方法一样的作用,可以实现线程同步,但比同步代码块/方法更强大。
因为显式锁Lock是一个接口,所以提供具体锁操作的是它的实现类。
我们来看看Lock接口的实现类有哪些:
ReentrantLockReentrantReadWriteLock.ReadLockReentrantReadWriteLock.WriteLock
这些实现类后面章节也会依次讲解到。
说了这么多,还没展示怎么使用显式锁Lock,接下来我们就来在使用中讲解更多关于显式锁Lock的特性与作用。
4.一次只有一个线程可以获取锁
这个知识点在《“全栈2019”Java多线程第十七章:同步锁详解》一章中也着重介绍过。
一次只有一个线程可以获取锁,并且对共享资源的所有访问都需要首先获取锁。
但是,某些锁可能允许并发访问共享资源,例如读写锁的读锁定。读写锁后面我们再单独出一章教程。
接下来,我们来用显式锁Lock改写上一小节的例子。
创建Lock接口的实现类ReentrantLock对象:
有显式锁之后就不要同步代码块/方法,去掉同步代码块/方法:
去掉之后,怎么获取锁?
5.获取锁lock()方法
我们可以使用Lock实现类实例的lock()方法来获取锁。
lock()方法在Lock接口中的源码:
将注释翻译成中文:
中文注释全文:
获得锁。
如果锁定不可用,则当前线程将被禁止用以进行线程调度,并且在获取锁定之前处于休眠状态。
子类实现该方法注意事项
Lock实现可能能够检测到锁的错误使用,例如可能导致死锁的调用,并且可能在这种情况下抛出(未经检查的)异常。 必须通过Lock实现记录环境和异常类型。
去掉注释版:
lock()方法作用是获取锁。
访问权限
void:lock()方法没有返回值。
lock()方法只能被对象调用。
参数
无。
抛出的异常
无。
应用
在开始需要同步的地方调用Lock实现类实例的lock()方法获取锁:
运行程序,执行结果:
从运行结果来看,程序经过显式锁改写之后没什么问题。但是,这种程序看似运行没问题,实则有很大的问题:一直在获取锁,却没释放锁。
一直在获取锁,没有释放锁,为什么程序没有报错呢?
那是因为我们的同步锁是可重入锁(即ReentrantLock):
什么是可重入锁?
具体的下一章讲解,这里简单来说就是每次执行需要相同锁的同步代码块/方法时不必再去重复获取锁也不必因为该锁被获取了一次而等待,只让锁的被获取次数+1即可,释放锁时被获取次数-1,当锁的被获取次数为0时线程才可以竞争该锁。
没理解的小伙伴没关系,下一章着重讲解可重入锁。
这里,我们将while循环去掉,看看程序有什么变化:
运行程序,执行结果:
从运行结果来看,程序在“Thread-0”线程执行完之后便不动了,这是因为“Thread-0”线程拿到锁之后,执行完代码,没有释放锁,其他线程拿不到锁只有等待,于是程序就一直阻塞在这。
上述问题说明获取锁之后,需要在结束同步的地方释放锁。
6.释放锁unlock()方法
我们可以使用Lock实现类实例的unlock()方法来获取锁。
unlock()方法在Lock接口中的源码:
将注释翻译成中文:
中文注释全文:
释放锁。
实现注意事项
实现通常会对哪个线程释放锁定(通常只有锁的持有者可以释放它)施加限制,并且如果违反了限制,则可能抛出(未经检查的)异常。 Lock实现必须记录任何限制和异常类型。
去掉注释版:
unlock()方法作用是释放锁。
访问权限
void:unlock()方法没有返回值。
unlock()方法只能被对象调用。
参数
无。
抛出的异常
无。
应用
在结束同步的地方调用Lock实现类实例的unlock()方法释放锁:
运行程序,执行结果:
从运行结果来看,程序阻塞的问题得到了解决。
再把while语句加上:
运行程序,执行结果:
从运行结果来看,程序虽然没有什么错误,但为什么总是一个叫“Thread-2”的线程一直在运行?这显得对其他线程很不公平。
这个问题可以解决吗?
由于文章篇幅原因,我们下章讲解。
7.将获取/释放锁操作放在try-catch-finally代码块中
这里大家需要特别注意一下,推荐将获取/释放锁操作放在try-catch-finally代码块中:
如上图所示。
最后,希望大家可以把这个例子照着写一遍,然后再自己默写一遍,方便以后碰到类似的面试题可以轻松应对。
祝大家编码愉快!
GitHub
本章程序GitHub地址:
总结显式锁Lock提供了比使用同步代码块/方法获得的更广泛的锁操作。lock()方法作用是获取锁。unlock()方法作用是释放锁。
至此,Java中Lock获取lock/释放unlock锁相关内容讲解先告一段落,更多内容请持续关注。
答疑
如果大家有问题或想了解更多前沿技术,请在下方留言或评论,我会为大家解答。
上一章
“全栈2019”Java多线程第二十六章:同步方法生产者与消费者线程
下一章
“全栈2019”Java多线程第二十八章:公平锁与非公平锁详解
学习小组
加入同步学习小组,共同交流与进步。
方式一:关注头条号Gorhaf,私信“Java学习小组”。方式二:关注公众号Gorhaf,回复“Java学习小组”。全栈工程师学习计划
关注我们,加入“全栈工程师学习计划”。
版权声明
原创不易,未经允许不得转载!
标签: #lock判断是否获取锁