并发编程(三)—— ReentrantLock的用法
ReentrantLock是Java并发包中提供的一个可重入的互斥锁。ReentrantLock和synchronized在基本用法,行为语义上都是类似的,同样都具有可重入性。只不过相比原生的Synchronized,ReentrantLock增加了一些高级的扩展功能,比如它可以实现公平锁,同时也可以绑定多个Conditon。
可重入性/公平锁/非公平锁
可重入性
所谓的可重入性,就是可以支持一个线程对锁的重复获取,原生的synchronized就具有可重入性,一个用synchronized修饰的递归方法,当线程在执行期间,它是可以反复获取到锁的,而不会出现自己把自己锁死的情况。ReentrantLock也是如此,在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。
公平锁/非公平锁
所谓公平锁,顾名思义,意指锁的获取策略相对公平,当多个线程在获取同一个锁时,必须按照锁的申请时间来依次获得锁,排排队,不能插队;非公平锁则不同,当锁被释放时,等待中的线程均有机会获得锁。synchronized是非公平锁,ReentrantLock默认也是非公平的,但是可以通过带boolean参数的构造方法指定使用公平锁,但非公平锁的性能一般要优于公平锁。
synchronized是Java原生的互斥同步锁,使用方便,对于synchronized修饰的方法或同步块,无需再显式释放锁。而ReentrantLock做为API层面的互斥锁,需要显式地去加锁解锁。采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
class X {
private final ReentrantLock lock = new ReentrantLock();
// ... public void m() {
lock.lock(); // 加锁
try {
// ... 函数主题
} finally {
lock.unlock() //解锁
}
}
}
源码分析
接下来我们从源码角度来看看ReentrantLock的实现原理,它是如何保证可重入性,又是如何实现公平锁的。
1、无参构造器(默认为非公平锁)
public ReentrantLock() {
sync = new NonfairSync();//默认是非公平的
}
sync是ReentrantLock内部实现的一个同步组件,它是Reentrantlock的一个静态内部类,继承于AQS。
2、带布尔值的构造器(是否公平)
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();//fair为true,公平锁;反之,非公平锁
}
此处可以指定是否采用公平锁,FailSync和NonFailSync亦为Reentrantlock的静态内部类,都继承于Sync。
3、lock()
public void lock() {
sync.lock();//代理到Sync的lock方法上
}
Sync的lock方法是抽象的,实际的lock会代理到FairSync或是NonFairSync上(根据用户的选择来决定,公平锁还是非公平锁)
4、unlock()
public void unlock() {
sync.release(1);//释放锁
}
释放锁,调用sync的release方法。
5、tryLock()
Lock lock = ...;
if(lock.tryLock()) {
try{
//处理任务
}catch(Exception ex){ }finally{
lock.unlock(); //释放锁
}
}else {
//如果不能获取锁,则直接做其他事情
}
tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false。
6、newCondition()
public Condition newCondition() {
return sync.newCondition();
}
获取一个conditon,ReentrantLock支持多个Condition
7、await()
public class MyService { private Lock lock = new ReentrantLock();
private Condition condition=lock.newCondition();
public void testMethod() { try {
lock.lock();
System.out.println("开始wait");
condition.await();
for (int i = 0; i < 5; i++) {
System.out.println("ThreadName=" + Thread.currentThread().getName()
+ (" " + (i + 1)));
}
} catch (InterruptedException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
finally
{
lock.unlock();
}
} }
通过创建Condition对象来使线程wait,必须先执行lock.lock方法获得锁
8、signal()
public void signal() {
try {
lock.lock();
condition.signal();
} finally {
lock.unlock();
}
}
condition对象的signal方法可以唤醒wait线程
9、创建多个condition对象
一个condition对象的signal(signalAll)方法和该对象的await方法是一一对应的,也就是一个condition对象的signal(signalAll)方法不能唤醒其他condition对象的await方法
ABC循环打印20遍
package main.java.Juc; import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; /*
* 编写一个程序,开启 3 个线程,这三个线程的 ID 分别为 A、B、C,每个线程将自己的 ID 在屏幕上打印 10 遍,要求输出的结果必须按顺序显示。
* 如:ABCABCABC…… 依次递归
*/
public class TestABCAlternate { public static void main(String[] args) {
AlternateDemo ad = new AlternateDemo(); new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
ad.loopA(i);
}
}
}, "A").start(); new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
ad.loopB(i);
}
}
}, "B").start(); new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
ad.loopC(i);
System.out.println("-----------------------------------");
}
}
}, "C").start();
} } class AlternateDemo{ private int number = 1; //当前正在执行线程的标记 private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition(); /**
* @param totalLoop : 循环第几轮
*/
public void loopA(int totalLoop){
lock.lock();
try {
//1. 判断
if(number != 1){
condition1.await();
}
//2. 打印
for (int i = 1; i <= 1; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
}
//3. 唤醒
number = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} public void loopB(int totalLoop){
lock.lock();
try {
//1. 判断
if(number != 2){
condition2.await();
}
//2. 打印
for (int i = 1; i <= 1; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
}
//3. 唤醒
number = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} public void loopC(int totalLoop){
lock.lock();
try {
//1. 判断
if(number != 3){
condition3.await();
}
//2. 打印
for (int i = 1; i <= 1; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);
}
//3. 唤醒
number = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} }
运行结果:
代码分析:
三个线程分别循环20次调用loopA、loopB、loopC打印,但是不确定是哪个方法先被调用到,如果是loopB先调用,则loopB方法先获取到锁,loopA和loopC等待锁,此时线程执行标记number=1,代码84行处为true,则condition2.await();如果需要唤醒此线程,则需要用condition2来唤醒,此时线程交出锁;
如果loopA获取了锁,loopB和loopC等待锁,此时线程执行标记number=1,代码63行处为false,则执行67行打印,打印完则用condition2.signal()唤醒打印loopB的线程,接着loopB的线程去打印B,线程loopB打印完毕去唤醒打印loopC的线程,打印完loopC再唤醒loopA,如此循环20次。
总结
1、Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2、synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3、Lock类可以创建Condition对象,Condition对象用来是线程等待和唤醒线程,需要注意的是Condition对象的唤醒的是用同一个Condition执行await方法的线程,所以也就可以实现唤醒指定类的线程
并发编程(三)—— ReentrantLock的用法的更多相关文章
- java并发编程——通过ReentrantLock,Condition实现银行存取款
java.util.concurrent.locks包为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器.该框架允许更灵活地使用锁和条件,但以更难用的语法为代价. Lock 接口 ...
- Java并发编程三个性质:原子性、可见性、有序性
并发编程 并发程序要正确地执行,必须要保证其具备原子性.可见性以及有序性:只要有一个没有被保证,就有可能会导致程序运行不正确 线程不安全在编译.测试甚至上线使用时,并不一定能发现,因为受到当时的 ...
- 多线程高并发编程(3) -- ReentrantLock源码分析AQS
背景: AbstractQueuedSynchronizer(AQS) public abstract class AbstractQueuedSynchronizer extends Abstrac ...
- 【Java并发编程三】闭锁
1.什么是闭锁? 闭锁(latch)是一种Synchronizer(Synchronizer:是一个对象,它根据本身的状态调节线程的控制流.常见类型的Synchronizer包括信号量.关卡和闭锁). ...
- 从缓存入门到并发编程三要素详解 Java中 volatile 、final 等关键字解析案例
引入高速缓存概念 在计算机在执行程序时,以指令为单位来执行,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入. 由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这 ...
- Java并发编程(三):ReentrantLock
ReentrantLock是可以用来代替synchronized的.ReentrantLock比synchronized更加灵活,功能上面更加丰富,性能方面自synchronized优化后两者性能没有 ...
- Java并发编程基础-ReentrantLock的机制
同步锁: 我们知道,锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源,在Lock接口出现之前,Java应用程序只能依靠synchronized关键字来实现同步锁 ...
- java并发系列(三)-----ReentrantLock(重入锁)功能详解和应用演示
1. ReentrantLock简介 jdk中独占锁的实现除了使用关键字synchronized外,还可以使用ReentrantLock.虽然在性能上ReentrantLock和synchronize ...
- 【java并发编程】ReentrantLock 可重入读写锁
目录 一.ReentrantLock可重入锁 二.ReentrantReadWriteLock读写锁 三.读锁之间不互斥 欢迎关注我的博客,更多精品知识合集 一.ReentrantLock可重入锁 可 ...
- 并发编程(三) IO模型
五 IO模型 常用的IO模型有4种: 阻塞IO 非阻塞IO IO多路复用 异步IO 不常用的有: 驱动信号 5.1 阻塞IO.非阻塞IO 阻塞IO:进程不能做其他的事情 非阻塞IO:等待数据无阻塞 阻 ...
随机推荐
- Dalvik和ART
--摘自<Android进阶解密> DVM和ART都是在Zygote进程中诞生的 *DVM和JVM的区别* 1.基于的架构不同 DVM是基于寄存器的,它没有基于栈的虚拟机在复制数据时而使用 ...
- CodeForces 510C Fox And Names (拓扑排序)
<题目链接> 题目大意: 给你一些只由小写字母组成的字符串,现在按一定顺序给出这些字符串,问你怎样从重排字典序,使得这些字符串按字典序排序后的顺序如题目所给的顺序相同. 解题分析:本题想到 ...
- CICD - Teamcity 配置之一: 数据库自动部署
数据库开发过程不是一次完成的,也是一个迭代的过程.如何快速开发和部署新的数据库对象,部署到不同的环境中,还有就是可不可以快速重建数据库对象,这里不包括数据部分. 首先从官网下载Teamcity:htt ...
- c++继承学习
继承分类: 虚表继承 class D{ public : d(){ } ~d(){ } private: }; 单重继承 class D{ public : d(){ } ~d(){ } privat ...
- Ubuntu 服务器设置软件多用户访问
假设在用户A下安装了软件xx 路径写入$home/.bashrc 这时该软件只有该用户可以使用 若要其他用户也能使用,只需要将该.bashrc拷贝到其他user的$home目录就行了
- 在Github上为项目添加多个用户
点击项目目录中的Settings 点击Collaborators 添加后,合作者会受到确认邮件,等待合作者确认后,合作者就可以提交了 添加组织 在Settings中找到Organizations 点击 ...
- Python(day1)
一.Python的属于解释型语言. 编译型:一次性,将全部的程序编译成二进制文件,然后再运行. 优点:运行速度快. 缺点:开发效率低,不能跨平台. 解释型:当你的程序运行时,一行一行的解释,并运行. ...
- h5的video下载按钮如何隐藏
h5的video下载按钮在部分手机系统上是不可用的,想要把这个按钮屏蔽掉,可苦于没有像屏蔽全屏按钮那样的伪类video::-webkit-media-controls-fullscreen-butto ...
- 使用kolin开发你的android应用
转载请注明出处,谢谢! 前段时间花了大概三周时间学习了kotlin,借着kotlin正好发布1.2,使用kotlin撸了一个android demo Github地址:https://github.c ...
- vue 登录跳转
前几次做登录处理,都是写一个公用方法,然后在对应的路由页面调用,即判断是不是处于登录状态,如果不是,就返回登录页面. let exit = (vm)=>{ let login = session ...