Java多线程基础——Lock类
发布时间 - 2026-01-10 22:49:31 点击率:次之前已经说道,JVM提供了synchronized关键字来实现对变量的同步访问以及用wait和notify来实现线程间通信。在jdk1.5以后,JAVA提供了Lock类来实现和synchronized一样的功能,并且还提供了Condition来显示线程间通信。

Lock类是Java类来提供的功能,丰富的api使得Lock类的同步功能比synchronized的同步更强大。本文章的所有代码均在Lock类例子的代码
本文主要介绍一下内容:
- Lock类
- Lock类其他功能
- Condition类
- Condition类其他功能
- 读写锁
Lock类
Lock类实际上是一个接口,我们在实例化的时候实际上是实例化实现了该接口的类Lock lock = new ReentrantLock();。用synchronized的时候,synchronized可以修饰方法,或者对一段代码块进行同步处理。
前面讲过,针对需要同步处理的代码设置对象监视器,比整个方法用synchronized修饰要好。Lock类的用法也是这样,通过Lock对象lock,用lock.lock来加锁,用lock.unlock来释放锁。在两者中间放置需要同步处理的代码。
具体的例子如下:
public class MyConditionService {
private Lock lock = new ReentrantLock();
public void testMethod(){
lock.lock();
for (int i = 0 ;i < 5;i++){
System.out.println("ThreadName = " + Thread.currentThread().getName() + (" " + (i + 1)));
}
lock.unlock();
}
}
测试的代码如下:
MyConditionService service = new MyConditionService(); new Thread(service::testMethod).start(); new Thread(service::testMethod).start(); new Thread(service::testMethod).start(); new Thread(service::testMethod).start(); new Thread(service::testMethod).start(); Thread.sleep(1000 * 5);
结果太长就不放出来,具体可以看我源码。总之,就是每个线程的打印1-5都是同步进行,顺序没有乱。
通过下面的例子,可以看出Lock对象加锁的时候也是一个对象锁,持续对象监视器的线程才能执行同步代码,其他线程只能等待该线程释放对象监视器。
public class MyConditionMoreService {
private Lock lock = new ReentrantLock();
public void methodA(){
try{
lock.lock();
System.out.println("methodA begin ThreadName=" + Thread.currentThread().getName() +
" time=" + System.currentTimeMillis());
Thread.sleep(1000 * 5);
System.out.println("methodA end ThreadName=" + Thread.currentThread().getName() +
" time=" + System.currentTimeMillis());
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void methodB(){
try{
lock.lock();
System.out.println("methodB begin ThreadName=" + Thread.currentThread().getName() +
" time=" + System.currentTimeMillis());
Thread.sleep(1000 * 5);
System.out.println("methodB end ThreadName=" + Thread.currentThread().getName() +
" time=" + System.currentTimeMillis());
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
测试代码如下:
public void testMethod() throws Exception {
MyConditionMoreService service = new MyConditionMoreService();
ThreadA a = new ThreadA(service);
a.setName("A");
a.start();
ThreadA aa = new ThreadA(service);
aa.setName("AA");
aa.start();
ThreadB b = new ThreadB(service);
b.setName("B");
b.start();
ThreadB bb = new ThreadB(service);
bb.setName("BB");
bb.start();
Thread.sleep(1000 * 30);
}
public class ThreadA extends Thread{
private MyConditionMoreService service;
public ThreadA(MyConditionMoreService service){
this.service = service;
}
@Override
public void run() {
service.methodA();
}
}
public class ThreadB extends Thread{
private MyConditionMoreService service;
public ThreadB(MyConditionMoreService service){
this.service = service;
}
@Override
public void run() {
super.run();
service.methodB();
}
}
结果如下:
methodA begin ThreadName=A time=1485590913520 methodA end ThreadName=A time=1485590918522 methodA begin ThreadName=AA time=1485590918522 methodA end ThreadName=AA time=1485590923525 methodB begin ThreadName=B time=1485590923525 methodB end ThreadName=B time=1485590928528 methodB begin ThreadName=BB time=1485590928529 methodB end ThreadName=BB time=1485590933533
可以看出Lock类加锁确实是对象锁。针对同一个lock对象执行的lock.lock是获得对象监视器的线程才能执行同步代码 其他线程都要等待。
在这个例子中,加锁,和释放锁都是在try-finally。这样的好处是在任何异常发生的情况下,都能保障锁的释放。
Lock类其他的功能
如果Lock类只有lock和unlock方法也太简单了,Lock类提供了丰富的加锁的方法和对加锁的情况判断。主要有
- 实现锁的公平
- 获取当前线程调用lock的次数,也就是获取当前线程锁定的个数
- 获取等待锁的线程数
- 查询指定的线程是否等待获取此锁定
- 查询是否有线程等待获取此锁定
- 查询当前线程是否持有锁定
- 判断一个锁是不是被线程持有
- 加锁时如果中断则不加锁,进入异常处理
- 尝试加锁,如果该锁未被其他线程持有的情况下成功
实现公平锁
在实例化锁对象的时候,构造方法有2个,一个是无参构造方法,一个是传入一个boolean变量的构造方法。当传入值为true的时候,该锁为公平锁。默认不传参数是非公平锁。
公平锁:按照线程加锁的顺序来获取锁
非公平锁:随机竞争来得到锁
此外,JAVA还提供isFair()来判断一个锁是不是公平锁。
获取当前线程锁定的个数
Java提供了getHoldCount()方法来获取当前线程的锁定个数。所谓锁定个数就是当前线程调用lock方法的次数。一般一个方法只会调用一个lock方法,但是有可能在同步代码中还有调用了别的方法,那个方法内部有同步代码。这样,getHoldCount()返回值就是大于1。
下面的方法用来判断等待锁的情况
获取等待锁的线程数
Java提供了getQueueLength()方法来得到等待锁释放的线程的个数。
查询指定的线程是否等待获取此锁定
Java提供了hasQueuedThread(Thread thread)查询该Thread是否等待该lock对象的释放。
查询是否有线程等待获取此锁定
同样,Java提供了一个简单判断是否有线程在等待锁释放即hasQueuedThreads()。
下面的方法用来判断持有锁的情况
查询当前线程是否持有锁定
Java不仅提供了判断是否有线程在等待锁释放的方法,还提供了是否当前线程持有锁即isHeldByCurrentThread(),判断当前线程是否有此锁定。
判断一个锁是不是被线程持有
同样,Java提供了简单判断一个锁是不是被一个线程持有,即isLocked()
下面的方法用来实现多种方式加锁
加锁时如果中断则不加锁,进入异常处理
Lock类提供了多种选择的加锁方法,lockInterruptibly()也可以实现加锁,但是当线程被中断的时候,就会加锁失败,进行异常处理阶段。一般这种情况出现在该线程已经被打上interrupted的标记了。
尝试加锁,如果该锁未被其他线程持有的情况下成功
Java提供了tryLock()方法来进行尝试加锁,只有该锁未被其他线程持有的基础上,才会成功加锁。
上面介绍了Lock类来实现代码的同步处理,下面介绍Condition类来实现wait/notify机制。
Condition类
Condition是Java提供了来实现等待/通知的类,Condition类还提供比wait/notify更丰富的功能,Condition对象是由lock对象所创建的。但是同一个锁可以创建多个Condition的对象,即创建多个对象监视器。这样的好处就是可以指定唤醒线程。notify唤醒的线程是随机唤醒一个。
下面,看一个例子,显示简单的等待/通知
public class ConditionWaitNotifyService {
private Lock lock = new ReentrantLock();
public Condition condition = lock.newCondition();
public void await(){
try{
lock.lock();
System.out.println("await的时间为 " + System.currentTimeMillis());
condition.await();
System.out.println("await结束的时间" + System.currentTimeMillis());
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void signal(){
try{
lock.lock();
System.out.println("sign的时间为" + System.currentTimeMillis());
condition.signal();
}finally {
lock.unlock();
}
}
}
测试的代码如下:
ConditionWaitNotifyService service = new ConditionWaitNotifyService(); new Thread(service::await).start(); Thread.sleep(1000 * 3); service.signal(); Thread.sleep(1000);
结果如下:
await的时间为 1485610107421 sign的时间为1485610110423 await结束的时间1485610110423
condition对象通过lock.newCondition()来创建,用condition.await()来实现让线程等待,是线程进入阻塞。用condition.signal()来实现唤醒线程。唤醒的线程是用同一个conditon对象调用await()方法而进入阻塞。并且和wait/notify一样,await()和signal()也是在同步代码区内执行。
此外看出await结束的语句是在获取通知之后才执行,确实实现了wait/notify的功能。下面这个例子是展示唤醒制定的线程。
ConditionAllService service = new ConditionAllService();
Thread a = new Thread(service::awaitA);
a.setName("A");
a.start();
Thread b = new Thread(service::awaitB);
b.setName("B");
b.start();
Thread.sleep(1000 * 3);
service.signAAll();
Thread.sleep(1000 * 4);
结果如下:
begin awaitA时间为 1485611065974ThreadName=A begin awaitB时间为 1485611065975ThreadName=B signAll的时间为1485611068979ThreadName=main end awaitA时间为1485611068979ThreadName=A
该结果确实展示用同一个condition对象来实现等待通知。
对于等待/通知机制,简化而言,就是等待一个条件,当条件不满足时,就进入等待,等条件满足时,就通知等待的线程开始执行。为了实现这种功能,需要进行wait的代码部分与需要进行通知的代码部分必须放在同一个对象监视器里面。执行才能实现多个阻塞的线程同步执行代码,等待与通知的线程也是同步进行。对于wait/notify而言,对象监视器与等待条件结合在一起 即synchronized(对象)利用该对象去调用wait以及notify。但是对于Condition类,是对象监视器与条件分开,Lock类来实现对象监视器,condition对象来负责条件,去调用await以及signal。
Condition类的其他功能
和wait类提供了一个最长等待时间,awaitUntil(Date deadline)在到达指定时间之后,线程会自动唤醒。但是无论是await或者awaitUntil,当线程中断时,进行阻塞的线程会产生中断异常。Java提供了一个awaitUninterruptibly的方法,使即使线程中断时,进行阻塞的线程也不会产生中断异常。
读写锁
Lock类除了提供了ReentrantLock的锁以外,还提供了ReentrantReadWriteLock的锁。读写锁分成两个锁,一个锁是读锁,一个锁是写锁。读锁与读锁之间是共享的,读锁与写锁之间是互斥的,写锁与写锁之间也是互斥的。
看下面的读读共享的例子:
public class ReadReadService {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void read(){
try{
try{
lock.readLock().lock();
System.out.println("获得读锁" + Thread.currentThread().getName() +
" " + System.currentTimeMillis());
Thread.sleep(1000 * 10);
}finally {
lock.readLock().unlock();
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
测试的代码和结果如下:
ReadReadService service = new ReadReadService();
Thread a = new Thread(service::read);
a.setName("A");
Thread b = new Thread(service::read);
b.setName("B");
a.start();
b.start();
结果如下:
获得读锁A 1485614976979 获得读锁B 1485614976981
两个线程几乎同时执行同步代码。
下面的例子是写写互斥的例子
public class WriteWriteService {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void write(){
try{
try{
lock.writeLock().lock();
System.out.println("获得写锁" + Thread.currentThread().getName() +
" " +System.currentTimeMillis());
Thread.sleep(1000 * 10);
}finally {
lock.writeLock().unlock();
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
测试代码和结果如下:
WriteWriteService service = new WriteWriteService();
Thread a = new Thread(service::write);
a.setName("A");
Thread b = new Thread(service::write);
b.setName("B");
a.start();
b.start();
Thread.sleep(1000 * 30);
结果如下:
获得写锁A 1485615316519 获得写锁B 1485615326524
两个线程同步执行代码
读写互斥的例子:
public class WriteReadService {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void read(){
try{
try{
lock.readLock().lock();
System.out.println("获得读锁" + Thread.currentThread().getName()
+ " " + System.currentTimeMillis());
Thread.sleep(1000 * 10);
}finally {
lock.readLock().unlock();
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
public void write(){
try{
try{
lock.writeLock().lock();
System.out.println("获得写锁" + Thread.currentThread().getName()
+ " " + System.currentTimeMillis());
Thread.sleep(1000 * 10);
}finally {
lock.writeLock().unlock();
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
测试的代码如下:
WriteReadService service = new WriteReadService();
Thread a = new Thread(service::write);
a.setName("A");
a.start();
Thread.sleep(1000);
Thread b = new Thread(service::read);
b.setName("B");
b.start();
Thread.sleep(1000 * 30);
结果如下:
获得写锁A 1485615633790 获得读锁B 1485615643792
两个线程读写之间也是同步执行代码。
总结
本文介绍了新的同步代码的方式Lock类以及新的等待/通知机制的实现Condition类。本文只是很简单的介绍了他们的概念和使用的方式。关于Condition以及读写锁还有更多的内容,将放在以后的博客中。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,同时也希望多多支持!
# java多线程lock
# 多线程lock
# Java多线程之同步锁-lock详解
# Java多线程中Lock锁的使用小结
# Java多线程中Lock锁的使用总结
# Java多线程编程之Lock用法实例
# Java多线程中Lock的使用小结
# 加锁
# 来实现
# 时间为
# 是在
# 多个
# 方法来
# 未被
# 放在
# 互斥
# 情况下
# 可以看出
# 在等待
# 都是
# 判断是否
# 是一个
# 实现了
# 他们的
# 就会
# 在这个
# 都要
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
利用python获取某年中每个月的第一天和最后一天
潮流网站制作头像软件下载,适合母子的网名有哪些?
制作无缝贴图网站有哪些,3dmax无缝贴图怎么调?
如何快速搭建高效WAP手机网站?
Laravel Octane如何提升性能_使用Laravel Octane加速你的应用
如何在VPS电脑上快速搭建网站?
Python面向对象测试方法_mock解析【教程】
Laravel中DTO是什么概念_在Laravel项目中使用数据传输对象(DTO)
如何注册花生壳免费域名并搭建个人网站?
EditPlus中的正则表达式 实战(4)
如何用PHP快速搭建高效网站?分步指南
Laravel如何获取当前用户信息_Laravel Auth门面获取用户ID
Laravel如何安装Breeze扩展包_Laravel用户注册登录功能快速实现【流程】
如何快速搭建高效可靠的建站解决方案?
详解Nginx + Tomcat 反向代理 如何在高效的在一台服务器部署多个站点
如何在阿里云服务器自主搭建网站?
如何在万网开始建站?分步指南解析
Laravel如何使用Guzzle调用外部接口_Laravel发起HTTP请求与JSON数据解析【详解】
如何在 Telegram Web View(iOS)中防止键盘遮挡底部输入框
ChatGPT回答中断怎么办 引导AI继续输出完整内容的方法
手机网站制作与建设方案,手机网站如何建设?
MySQL查询结果复制到新表的方法(更新、插入)
南京网站制作费用,南京远驱官方网站?
Laravel定时任务怎么设置_Laravel Crontab调度器配置
微信h5制作网站有哪些,免费微信H5页面制作工具?
如何用IIS7快速搭建并优化网站站点?
php在windows下怎么调试_phpwindows环境调试操作说明【操作】
如何在局域网内绑定自建网站域名?
iOS UIView常见属性方法小结
Linux后台任务运行方法_nohup与&使用技巧【技巧】
瓜子二手车官方网站在线入口 瓜子二手车网页版官网通道入口
html5的keygen标签为什么废弃_替代方案说明【解答】
Laravel怎么实现微信登录_Laravel Socialite第三方登录集成
如何在Windows 2008云服务器安全搭建网站?
为什么php本地部署后css不生效_静态资源加载失败修复技巧【技巧】
Laravel如何使用Socialite实现第三方登录?(微信/GitHub示例)
网站制作软件免费下载安装,有哪些免费下载的软件网站?
Laravel如何发送邮件_Laravel Mailables构建与发送邮件的简明教程
如何快速选择适合个人网站的云服务器配置?
长沙做网站要多少钱,长沙国安网络怎么样?
Laravel如何使用模型观察者?(Observer代码示例)
免费的流程图制作网站有哪些,2025年教师初级职称申报网上流程?
如何在 Pandas 中基于一列条件计算另一列的分组均值
Laravel如何发送系统通知?(Notification渠道示例)
移动端手机网站制作软件,掌上时代,移动端网站的谷歌SEO该如何做?
如何在IIS中新建站点并配置端口与物理路径?
Laravel怎么实现软删除SoftDeletes_Laravel模型回收站功能与数据恢复【步骤】
个人摄影网站制作流程,摄影爱好者都去什么网站?
如何快速辨别茅台真假?关键步骤解析
高性能网站服务器配置指南:安全稳定与高效建站核心方案

