多线程系列(八) -ReentrantLock基本用法介绍
一、简介
在之前的线程系列文章中,我们介绍到了使用synchronized
关键字可以实现线程同步安全的效果,以及采用wait()
、notify()
和notifyAll()
方法,可以实现多个线程之间的通信协调,基本可以满足并发编程的需求。
但是采用synchronized
进行加锁,这种锁一般都比较重,里面的实现机制也非常复杂,同时获取锁时必须一直等待,没有额外的尝试机制,如果编程不当,可能就容易发生死锁现象。
从 JDK 1.5 开始,引入了一个高级的处理并发的java.util.concurrent
包,它提供了大量更高级的并发功能,能大大的简化多线程程序的编写。
比如我们今天要介绍的java.util.concurrent.locks
包提供的ReentrantLock
类,一个可重入的互斥锁,它具有与使用synchronized
加锁一样的特性,并且功能更加强大。
下面我们一起来学习一下ReentrantLock
类的基本使用。
二、ReentrantLock 基本用法
在介绍ReentrantLock
之前,我们先来看一下传统的使用synchronized
对方法进行加锁的示例。
public class Counter {
private int count;
public void add() {
synchronized(this) {
count ++;
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", count:" + getCount());
}
}
public int getCount() {
return count;
}
}
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
// 创建5个线程,同时对count进行加一操作
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
counter.add();
}
}).start();
}
// 假设休眠1秒,5个线程执行完毕
Thread.sleep(1000);
System.out.println("count:" + counter.getCount());
}
输出结果如下:
ThreadName:Thread-0, count:1
ThreadName:Thread-1, count:2
ThreadName:Thread-2, count:3
ThreadName:Thread-3, count:4
ThreadName:Thread-4, count:5
count:5
如果用ReentrantLock
替代,只需要将Counter
中的代码改造为如下:
public class Counter {
private final Lock lock = new ReentrantLock();
private int count;
public void add() {
// 加锁
lock.lock();
try {
count ++;
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", count:" + getCount());
} finally {
// 释放锁
lock.unlock();
}
}
public int getCount() {
return count;
}
}
运行程序,结果与上面一致,可以证明:ReentrantLock
具备与synchronized
一样的加锁功能。
同时,ReentrantLock
还具备在指定的时间内尝试获取锁的机制,比如下面这行代码:
if (lock.tryLock(3, TimeUnit.SECONDS)) {
try {
...
} finally {
lock.unlock();
}
}
尝试在 3 秒内获取锁,如果获取不到就返回false
,程序不需要无限等待下去,这个功能在实际开发中使用非常的广泛。
从上面的示例代码,我们可以总结出synchronized
和ReentrantLock
有以下几点不一样。
ReentrantLock
需要手动调用加锁方法;而synchronized
不需要,它采用了隐藏的加锁方式,借助 jvm 来实现synchronized
不需要考虑异常;而ReentrantLock
获取锁之后,要在finally
中正确的释放锁,否则会影响其它线程ReentrantLock
拥有尝试获取锁的超时机制,利用它可以避免无限等待;而synchronized
不具备synchronized
是 Java 语言层面提供的语法;而ReentrantLock
是 Java 代码实现的可重入锁
因此,在并发编程中,使用ReentrantLock
比直接使用synchronized
更灵活、更安全,采用tryLock(long time, TimeUnit unit)
方法,即使未获取到锁也不会导致死锁。
三、ReentrantLock 和 synchronized 持有的对象监视器是同一个吗?
可能有的同学会发出这样的一个问题,使用ReentrantLock
进行加锁和使用synchronized
加锁,两者持有的对象监视器是同一个吗?
下面我们一起来看一个例子。
public class Counter {
private final Lock lock = new ReentrantLock();
private int count;
public synchronized void methodA() {
System.out.println("ThreadName:" + Thread.currentThread().getName() + ",begin methodA, count:" + getCount());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count ++;
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", count:" + getCount());
}
public void methodB() {
// 加锁
lock.lock();
try {
System.out.println("ThreadName:" + Thread.currentThread().getName() + ",begin methodB, count:" + getCount());
Thread.sleep(3000);
count ++;
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", count:" + getCount());
} catch (Exception e){
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
}
public int getCount() {
return count;
}
}
public class MyThreadA extends Thread {
private Counter counter;
public MyThreadA(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
counter.methodA();
}
}
public class MyThreadB extends Thread {
private Counter counter;
public MyThreadB(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
counter.methodB();
}
}
public class MyThreadTest {
public static void main(String[] args) {
Counter counter = new Counter();
MyThreadA threadA = new MyThreadA(counter);
threadA.start();
MyThreadB threadB = new MyThreadB(counter);
threadB.start();
}
}
看一下运行结果:
ThreadName:Thread-0,begin methodA, count:0
ThreadName:Thread-1,begin methodB, count:0
ThreadName:Thread-0, count:2
ThreadName:Thread-1, count:2
从日志上可以看出,采用两个线程分别采用synchronized
和ReentrantLock
两种加锁方式对count
进行操作,两个线程交替执行,可以得出一个结论:synchronized
和ReentrantLock
持有的对象监视器不同。
四、Condition 基本用法
在之前的文章中,我们介绍了在synchronized
同步方法/代码块中,使用wait()
、notify()
和notifyAll()
可以实现线程之间的等待/通知模型。
ReentrantLock
同样也可以,只需要借助Condition
类即可实现,Condition
提供的await()
、signal()
、signalAll()
原理和synchronized
锁对象的wait()
、notify()
、notifyAll()
是一致的,并且其行为也是一样的。
我们还是先来看一个简单的示例。
public class Counter {
private final Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private int count;
public void await(){
// 加锁
lock.lock();
try {
condition.await();
System.out.println("await等待结束,count:" + getCount());
} catch (Exception e){
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
}
public void signal(){
// 加锁
lock.lock();
try {
count++;
// 唤醒某个等待线程
condition.signal();
// 唤醒所有等待线程
// condition.signalAll();
System.out.println("signal 唤醒通知完毕");
} catch (Exception e){
e.printStackTrace();
} finally {
// 释放锁
lock.unlock();
}
}
public int getCount() {
return count;
}
}
public class MyThreadA extends Thread {
private Counter counter;
public MyThreadA(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
counter.await();
}
}
public class MyThreadB extends Thread {
private Counter counter;
public MyThreadB(Counter counter) {
this.counter = counter;
}
@Override
public void run() {
counter.signal();
}
}
public class MyThreadTest {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
// 先启动执行等待的线程
MyThreadA threadA = new MyThreadA(counter);
threadA.start();
Thread.sleep(3000);
// 过3秒,再启动执行通知的线程
MyThreadB threadB = new MyThreadB(counter);
threadB.start();
}
}
看一下运行结果:
signal 通知完毕
await等待结束,count:1
从结果上看很明显的看出,等待线程MyThreadA
先启动,过了 3 秒之后再启动了MyThreadB
,但是signal()
方法先执行完毕,再通知await()
方法执行,符合代码预期。
这个例子也证明了一点:condition.await()
方法是释放了锁,不然signal()
方法体不会被执行。
相比wait/notify/notifyAll
的等待/通知模型,Condition
更加灵活,理由有以下几点:
notify()
方法唤醒线程时,被通知的线程由 Java 虚拟机随机选择;而采用ReentrantLock
结合Condition
可以实现有选择性地通知,这一特性在实际编程中非常实用- 一个
Lock
里面可以创建多个Condition
实例,实现多路通知,使用多个Condition
的应用场景很常见,比如ArrayBlockingQueue
五、小结
本文主要围绕ReentrantLock
的基本使用做了一次简单的知识总结,如果有不正之处,请多多谅解,并欢迎批评指出。
六、参考
1、博客园 -五月的仓颉 - ReentrantLock的使用和Condition
七、写到最后
最近无意间获得一份阿里大佬写的技术笔记,内容涵盖 Spring、Spring Boot/Cloud、Dubbo、JVM、集合、多线程、JPA、MyBatis、MySQL 等技术知识。需要的小伙伴可以点击如下链接获取,资源地址:技术资料笔记。
不会有人刷到这里还想白嫖吧?点赞对我真的非常重要!在线求赞。加个关注我会非常感激!
多线程系列(八) -ReentrantLock基本用法介绍的更多相关文章
- java多线程系列(八)---CountDownLatch和CyclicBarrie
CountDownLatch 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 java多线 ...
- java多线程系列(四)---ReentrantLock的使用
Lock的使用 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我的理 ...
- 【多线程系列】AQS CAS简单介绍
一.什么是CAS CAS(Compare And Swap),即比较并交换.是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数——内存位置(V).预期原值(A)和新值(B). ...
- 多线程系列八:线程安全、Java内存模型(JMM)、底层实现原理
一.线程安全 1. 怎样让多线程下的类安全起来 无状态.加锁.让类不可变.栈封闭.安全的发布对象 2. 死锁 2.1 死锁概念及解决死锁的原则 一定发生在多个线程争夺多个资源里的情况下,发生的原因是 ...
- Java多线程系列八——volatile和ThreadLocal
参考资料: http://ifeve.com/java-memory-model-4/ http://www.infoq.com/cn/articles/java-memory-model-1 htt ...
- 【Java多线程系列八】volatile和ThreadLocal
1. volatile final class Singleton { private static Singleton instance = null; private Singleton() { ...
- Java多线程系列2 线程常见方法介绍
守护线程 执行一些非业务方法,比如gc.当全部都是守护线程的时候,jvm退出 线程优先级 设置线程优先级:setPriority(int priorityLevel).参数priorityLevel ...
- java多线程系列(九)---ArrayBlockingQueue源码分析
java多线程系列(九)---ArrayBlockingQueue源码分析 目录 认识cpu.核心与线程 java多线程系列(一)之java多线程技能 java多线程系列(二)之对象变量的并发访问 j ...
- java多线程系列 目录
Java多线程系列1 线程创建以及状态切换 Java多线程系列2 线程常见方法介绍 Java多线程系列3 synchronized 关键词 Java多线程系列4 线程交互(wait和 ...
- Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock
本章对ReentrantLock包进行基本介绍,这一章主要对ReentrantLock进行概括性的介绍,内容包括:ReentrantLock介绍ReentrantLock函数列表ReentrantLo ...
随机推荐
- [转帖]jmeter学习笔记(二十二)——监听器插件之jp@gc系列
一.jp@gc - Actiive Threads Over Time 不同时间活动用户数量展示 下面是一个阶梯加压测试的图标 二.jp@gc - Transactions per Second ...
- [转帖]Nginx应用调优案例
https://bbs.huaweicloud.com/blogs/146367 [摘要] 1 问题背景nginx的应用程序移植到TaiShan服务器上,发现业务吞吐量没有达到硬件预期,需要做相应调优 ...
- redis 6源码解析之 事件
redis的事件分为:文件事件和时间事件.文件事件是基于I/O的事务处理,时间事件则是基于时间点的事务处理.redis事件支持的多路复用包含四个实现:ae_epoll.c,ae_evport.c,ae ...
- React Hooks 指北
前言 这篇文章旨在总结 React Hooks 的使用技巧以及在使用过程中需要注意的问题,其中会附加一些问题产生的原因以及解决方式.但是请注意,文章中所给出的解决方式并不一定完全适用,解决问题的方案有 ...
- Mysql到TiDB迁移,双写数据库兜底方案
作者:京东零售 石磊 TiDB 作为开源 NewSQL 数据库的典型代表之一,同样支持 SQL,支持事务 ACID 特性.在通讯协议上,TiDB 选择与 MySQL 完全兼容,并尽可能兼容 MySQL ...
- 【DS】P9062 [Ynoi2002] Adaptive Hsearch&Lsearch(区间最近点对)
Problem Link 给定平面上 \(n\) 个点,\(Q\) 次询问编号在 \([l,r]\) 内的点的最近点对.\(n,Q\le 2.5\times 10^5\). 技巧:平面网格化 乱搞都是 ...
- vim 从嫌弃到依赖(22)——自动补全
这篇文章我们将讨论 vim 自带的自动补全功能.当然,针对自动补全功能有许多好用的插件,但是了解vim自带的功能有助于我们更好的用来插件的补全功能.因为我见过有的配置文件将插件的功能配置的比原有的更难 ...
- 什么是 Java 字节码?采用字节码的好处是什么?
在 Java 中,JVM 可以理解的代码就叫做字节码(即扩展名为 .class 的文件),它不面向任何特定的处理器,只面向虚拟机.Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效 ...
- Solon 框架启动为什么特别快?
思来想去!可能与 Solon 容器的独立设计有一定关系. 1.Solon 注解容器的运行特点 有什么注解要处理的(注解能力被规范成了四种),提前注册登记 全局只扫描一次,并在扫描过程中统一处理注解相关 ...
- 教你用JavaScript获取大转盘
案例介绍 欢迎来到我的小院,我是霍大侠,恭喜你今天又要进步一点点了!我们来用JavaScript编程实战案例,做一个大转盘.当你难以抉择的时候不妨用这个案例来帮你做选择.通过编程实战我们可以学到按钮的 ...