synchronized ReentrantLock 比较分析
在编写多线程代码的时候,对于不允许并发的代码,很多需要加锁进行处理。在进行加锁处理时候,synchronized作为java的内置锁,同时也是java关键字,最为被人熟知,即使是最初级的java程序员,只要知道java并发处理的,都会知道syschronized。
java5.0之后,java提供了另外一种加锁机制,ReentrantLock提供了更多更灵活的功能,在很多复杂场景下,ReentrantLock相比synchronized会更合适。
synchronized和ReentrantLock也经常会拿出来比较,这也是在面试中,面试官也经常会问到的一个问题。这里就简单整理一下synchronized与ReentrantLock的异同。
相同点
比较synchronized 和 ReentrantLock的相同点,毋庸置疑,它们的功能都是作为锁对访问进行控制。防止并发造成逻辑错误。
1、在获取ReentrantLock的时候,有着与synchronized相同的互斥性(互斥性:当一个线程已经获取到某个锁的时候,其他线程无法获取到该锁)
2、ReentrantLock 和 syschronized提供的相同的内存可见性(可见性:当一个线程修改一个变量的时候,其他的线程能够及时知道)
3、虽然ReentrantLock命名为可重入锁,但是实际上synchronized也是一样可重入的(可重入:当一个线程拿到某个锁后,当该线程在释放该锁之前,可以重复获取该锁)
打个比方:比如你去食堂排队打饭,饭桶只有一个饭勺。你手中拿着勺子,其他人要打饭就得等你打完,这就叫互斥性(独占性),勺子就是锁。你一开始左手拿着勺子,觉得姿势不爽,换成右手来拿勺子,此时勺子在你手里,你可以重复左手右手一个慢动作,这就叫可重入。当你打完饭的时候,饭桶里的饭明显少了很多(真的很能吃),其他来打饭的人马上就能看到了,这就叫可见性,饭桶是主存,你的饭盆就是工作内存。
不同点
写法
首先最直观的不同点,就是写法上的不同。
Object object = new Object();
synchronized (object) {
doSomeThing(s);
}
Lock lock=new ReentrantLock();
lock.lock();
try {
doSomeThing(s);
}finally {
lock.unlock();
}
synchronized的写法相比较更简单,使用synchronized会自动加锁,synchronized同步代码块退出后,会自动释放锁。
ReentrantLock需要在fiinally手动释放锁,如果忘记释放锁,会造成很大的麻烦。
synchronized必须写在同一个代码块中,无法进行拆分。ReentrantLock在可以在不同的地方进行lock,unLock,代码也可以进行灵活编写。比如concurrentHashMap中,Segment继承ReentrantLock来进行锁操作。
功能
synchronized提供的功能相对单一,当需要对锁进行复杂操作的时候,synchronized就会显得力不从心。比如轮询锁、定时锁、可中断锁。
轮询锁
第一种场景,假设有一段代码同时需要获取两个锁,当我们使用synchronized的时候,会这么写
Object object1 = new Object();
Object object2 = new Object();
synchronized (object1) {
synchronized (object2){
doSomeThing(object1,object2);
}
}
如果现在有另外一段代码,仍然需要同时锁住object1和object2。synchronized就必须保证同步的顺序,如果先锁住object2、再锁住object1,就有可能产生死锁。当一段程序用到多个锁,容易搞乱顺序。特别在团队开发的时候,成员之间不会知道他人通过什么样的顺序进行加锁。
这个时候ReentrantLock更加灵活的优点就体现出来了。ReentrantLock提供了tryLock()方法,我们可以用这个方法来实现轮询。
Lock lock1 = new ReentrantLock();
Lock lock2 = new ReentrantLock();
while (true) {
if (lock1.tryLock()) {
try {
if (lock2.tryLock()) {
try {
doSomeThing(s);
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
SECONDS.sleep(1);
}
使用ReentrantLock的tryLock(),如上述代码所示,当我们尝试获取lock1成功、获取lock2失败的时候,我们会释放lock1,休眠一秒后重新获取两个锁。这样的好处通过放弃已经获取到的锁避免了死锁的出现,代码的安全性更高。而且获取锁的过程中,我们可以灵活的插入一些代码,比如17行所示的,每当两个锁没有同时获取成功,我们就进行一秒钟休眠(虽然正常项目中下我们不会这么处理)。
定时锁
第二种场景,如果我们需要一个程序在特定时间内完成,如果特定时间内没有拿到锁,就直接返回,放弃该任务。synchronized面对这种场景同样为难,又到了ReentrantLock登场时间。reentrantLock的tryLock(long timeout, TimeUnit unit)方法提供了超时返回的功能。如下示例代码:
Lock lock = new ReentrantLock();
if(!lock.tryLock(1,SECONDS)){
return;
}
try {
doSomeThing();
}finally {
lock.unlock();
}
上例中,ReentrantLock尝试使用1秒的时候去获取锁,如果超过时间仍然没有获取到锁,则返回失败。如果获取锁成功,则继续执行目的代码。
可中断锁
在使用synchronized进行获取锁排队的时候,是无法响应中断的,当业务场景必须实时响应线程中断时,synchronized就不那么合适了。使用ReantrantLock可以使用lockInterruptibly()方法。该方法支持线程在获取锁的过程中,对线程进行中断。
Lock lock = new ReentrantLock();
lock.lockInterruptibly();
try {
doSomeThing(s);
} finally {
lock.unlock();
}
性能
在java 6.0之前,synchronized的性能方便比ReentrantLock弱一截,java 发布6.0之后,重新优化了synchronized的算法。ReentrantLock的性能仅比synchronized的性能稍微强一些。至于现在,在不同系统,不同硬件下,测试出来的性能都会有偏差。这边我也懒得自己再去做实验尝试,所以就不多逼逼了。
公平性
当多个线程先后请求一个被占用的锁的时候,当该锁释放,如果有算法保证最早排队的线程最先拿到锁,则这个锁就是公平锁。
synchronized是不公平,reentrantLock则同时提供了公平锁和不公平锁两种。公平锁的好处在于,线程的先后顺序,等待最久的线程先执行。然而公平锁的性能却不如非公平锁,原因在于,假设线程A持有一个锁,并且B线程请求这个锁。由于这个锁已经被A线程持有,因此B将被挂起。当A释放的锁的时候,B将被唤醒,然后重新尝试获取该锁。与此同时,如果C线程也同时请求该锁,那么C可能在B被完全唤醒之前获取该锁、并执行任务、释放锁。即线程C可能在A、B线程持有锁的间隔中,完成操作。B线程完全唤醒之后会发现锁已经被释放了。因此并不影响B线程的执行。通过这种插队行为,能提高这个程序的吞吐量。
默认创建的ReentrantLock都是非公平的锁,如果想创建一个公平锁,可以利用Reentrant的构造器。
//ReentrantLock 构造器
public ReentrantLock( boolean fair){
sync = fair ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync();
}
Lock lock = new ReentrantLock(true);
以下简单贴一下reentranLock的源码,看下公平锁和非公平锁的区别:
static final class FairSync extends ReentrantLock.Sync { //公平锁,直接进行队列
final void lock() {
acquire(1);
}
} static final class NonfairSync extends ReentrantLock.Sync { /**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
//非公平锁,可以进行插队
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
} protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
java语言规范并没有要求JVM以公平的方式来实现内置锁,各种JVM也确实没有这么做。ReentrantLock并没有进一步降低锁的公平性,在等待线程队列中,依旧遵循先进先出的原则。
总结
synchronized与ReentrantLock相比较之后,感觉ReeantrantLock比synchronized有更强大的功能,更灵活的操作。但是相比起来,syschronzied使用门槛更低,简单粗暴,而且不会出现忘记解锁的状况。《Java并发编程实战》中建议,在synchronized无法满足需求的时候,再用ReeantrantLock,原因大致如下:
1、synchronized简单易用,容易上手,而且不容易出现忘记解锁的情况
2、未来更可能提高synchronized的性能,因为synchronized是JVM的内置属性
synchronized ReentrantLock 比较分析的更多相关文章
- java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析
java多线程系列(五)---synchronized ReentrantLock volatile Atomic 原理分析 前言:如有不正确的地方,还望指正. 目录 认识cpu.核心与线程 java ...
- Java 重入锁 ReentrantLock 原理分析
1.简介 可重入锁ReentrantLock自 JDK 1.5 被引入,功能上与synchronized关键字类似.所谓的可重入是指,线程可对同一把锁进行重复加锁,而不会被阻塞住,这样可避免死锁的产生 ...
- synchronized,ReentrantLock解决锁冲突,脏读的问题
最常见的秒杀系统,解决思路就是从前端.后台服务.数据库层层去掉负载,以达到平衡 锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLo ...
- 各种同步方法性能比较(synchronized,ReentrantLock,Atomic)
synchronized: 在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的.原因在于,编译程序通常会尽可能的进行优化synchronize,另外可读性非常好,不 ...
- 通过ReentrantLock源代码分析AbstractQueuedSynchronizer独占模式
1. 重入锁的概念与作用 reentrant 锁意味着什么呢?简单来说,它有一个与获取锁相关的计数器,如果已占有锁的某个线程再次获取锁,那么lock方法中将计数器就加1后就会立刻返回.当释 ...
- 各种同步方法性能比较(synchronized,ReentrantLock,Atomic)
5.0的多线程任务包对于同步的性能方面有了很大的改进,在原有synchronized关键字的基础上,又增加了ReentrantLock,以及各种Atomic类.了解其性能的优劣程度,有助与我们在特定的 ...
- Java-JUC(十五):synchronized执行流程分析
一.锁对象及 synchronized 的使用 synchronized 通过互斥锁(Mutex Lock)来实现,同一时刻,只有获得锁的线程才可以执行锁内的代码. 锁对象分为两种: 实例对象(一个类 ...
- java 多线程实现四种方式解析Thread,Runnable,Callable,ServiceExcutor,Synchronized ,ReentrantLock
1.Thread实现: import java.util.Date; import java.text.SimpleDateFormat; public class MyThread extends ...
- ReentrantLock原理分析
一 UML类图 通过类图ReentrantLock是同步锁,同一时间只能有一个线程获取到锁,其他获取该锁的线程会被阻塞而被放入AQS阻塞队列中.ReentrantLock类继承Lock接口:内部抽象类 ...
随机推荐
- nginx分布式实例入门操作
本文目的 前段时间学习WCF已经渐入佳境,完成了既定学习目标,转入分布式系统学习.本文技术路线是: 采用wcf实现分布式服务端和客户端,客户端部署于本地主机,nginx和WCF部署于虚拟机端(分别是三 ...
- mybatis 处理枚举类型
MyBatis支持持久化enum类型属性.假设t_user表中有一列gender(性别)类型为 varchar2(10),存储 MALE 或者 FEMALE 两种值.并且,User对象有一个enum类 ...
- DRF的三大认证组件
目录 DRF的三大认证组件 认证组件 工作原理 实现 权限组件 工作原理 实现 频率组件 工作原理 实现 三种组件的配置 DRF的三大认证组件 认证组件 工作原理 首先,认证组件是基于BaseAuth ...
- javascript面向对象编程笔记(基本数据类型,数组,循环及条件表达式)
javascript面向对象编程指南 最近在看这本书,以下是我的笔记,仅供参考. 第二章 基本数据类型.数组.循环及条件表达式 2.1 变量 区分大小写 2.3 基本数据类型 数字:包括浮点数与整数 ...
- pytorch clamp 与clamp_区别
pytorch clamp 与clamp_ ,有下划线的表示修改并付给自身,无下划线的表示需要返回处理后的值,比如: h = k.clamp(min=0) #将结果存入h,k保留原值 k.clamp_ ...
- 2019-5-21-dotnet-使用-GC.GetAllocatedBytesForCurrentThread-获取当前线程分配过的内存大小...
title author date CreateTime categories dotnet 使用 GC.GetAllocatedBytesForCurrentThread 获取当前线程分配过的内存大 ...
- Codeforces Round #567 (Div. 2)自闭记
嘿嘿嘿,第一篇文章,感觉代码可以缩起来简直不要太爽 打个div2发挥都这么差... 平均一题fail一次,还调不出错,自闭了 又一次跳A开B,又一次B傻逼错误调不出来 罚时上天,E还傻逼了..本来这场 ...
- webgoat的构建
如何打开webgoat 1.ctrl+r 打开命令 2.cmd 打开命令编辑器 3.我的webgoat保存在E:/安全软件/webgoat-container-7.0.1-war-exec下 4.在 ...
- android studio 一个项目如何打包多个apk
1.修改app的build.gradle文件 假设我们同一套代码编译2个app:demo1和demo2 android { ... productFlavors { // demo1 demo1 { ...
- 树莓派3B+ 人脸识别、摄像头安装和使用
最近在学校里折腾树莓派上的人脸识别,折腾了很久才能用 在此记录下使用的过程和遇到的困难 过程基于超有趣!手把手教你使用树莓派实现实时人脸检测完成的.其中前面opencv的安装是文章中的Raspbian ...