Java:既然有了synchronized,为什么还要提供Lock?
摘要:在Java中提供了synchronized关键字来保证只有一个线程能够访问同步代码块。既然已经提供了synchronized关键字,那为何在Java的SDK包中,还会提供Lock接口呢?这是不是重复造轮子,多此一举呢?
本文分享自华为云社区《【高并发】Java中提供了synchronized,为什么还要提供Lock呢?》,作者: 冰 河。
在Java中提供了synchronized关键字来保证只有一个线程能够访问同步代码块。既然已经提供了synchronized关键字,那为何在Java的SDK包中,还会提供Lock接口呢?这是不是重复造轮子,多此一举呢?今天,我们就一起来探讨下这个问题。
再造轮子?
既然JVM中提供了synchronized关键字来保证只有一个线程能够访问同步代码块,为何还要提供Lock接口呢?这是在重复造轮子吗?Java的设计者们为何要这样做呢?让我们一起带着疑问往下看。
为何提供Lock接口?
很多小伙伴可能会听说过,在Java 1.5版本中,synchronized的性能不如Lock,但在Java 1.6版本之后,synchronized做了很多优化,性能提升了不少。那既然synchronized关键字的性能已经提升了,那为何还要使用Lock呢?
如果我们向更深层次思考的话,就不难想到了:我们使用synchronized加锁是无法主动释放锁的,这就会涉及到死锁的问题。
死锁问题
如果要发生死锁,则必须存在以下四个必要条件,四者缺一不可。
- 互斥条件
在一段时间内某资源仅为一个线程所占有。此时若有其他线程请求该资源,则请求线程只能等待。
- 不可剥夺条件
线程所获得的资源在未使用完毕之前,不能被其他线程强行夺走,即只能由获得该资源的线程自己来释放(只能是主动释放)。
- 请求与保持条件
线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他线程占有,此时请求线程被阻塞,但对自己已获得的资源保持不放。
- 循环等待条件
在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一个进程所深情地资源。
synchronized的局限性
如果我们的程序使用synchronized关键字发生了死锁时,synchronized关键是是无法破坏“不可剥夺”这个死锁的条件的。这是因为synchronized申请资源的时候, 如果申请不到, 线程直接进入阻塞状态了, 而线程进入阻塞状态, 啥都干不了, 也释放不了线程已经占有的资源。
然而,在大部分场景下,我们都是希望“不可剥夺”这个条件能够被破坏。也就是说对于“不可剥夺”这个条件,占用部分资源的线程进一步申请其他资源时, 如果申请不到, 可以主动释放它占有的资源, 这样不可剥夺这个条件就破坏掉了。
如果我们自己重新设计锁来解决synchronized的问题,我们该如何设计呢?
解决问题
了解了synchronized的局限性之后,如果是让我们自己实现一把同步锁,我们该如何设计呢?也就是说,我们在设计锁的时候,要如何解决synchronized的局限性问题呢?这里,我觉得可以从三个方面来思考这个问题。
(1)能够响应中断。 synchronized的问题是, 持有锁A后, 如果尝试获取锁B失败, 那么线程就进入阻塞状态, 一旦发生死锁, 就没有任何机会来唤醒阻塞的线程。 但如果阻塞状态的线程能够响应中断信号, 也就是说当我们给阻塞的线程发送中断信号的时候, 能够唤醒它, 那它就有机会释放曾经持有的锁A。 这样就破坏了不可剥夺条件了。
(2)支持超时。 如果线程在一段时间之内没有获取到锁, 不是进入阻塞状态, 而是返回一个错误, 那这个线程也有机会释放曾经持有的锁。 这样也能破坏不可剥夺条件。
(3)非阻塞地获取锁。 如果尝试获取锁失败, 并不进入阻塞状态, 而是直接返回, 那这个线程也有机会释放曾经持有的锁。 这样也能破坏不可剥夺条件。
体现在Lock接口上,就是Lock接口提供的三个方法,如下所示。
- // 支持中断的API
- void lockInterruptibly() throws InterruptedException;
- // 支持超时的API
- boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
- // 支持非阻塞获取锁的API
- boolean tryLock();
- lockInterruptibly()
支持中断。
- tryLock()方法
tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
- tryLock(long time, TimeUnit unit)方法
tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
也就是说,对于死锁问题,Lock能够破坏不可剥夺的条件,例如,我们下面的程序代码就破坏了死锁的不可剥夺的条件。
- public class TansferAccount{
- private Lock thisLock = new ReentrantLock();
- private Lock targetLock = new ReentrantLock();
- //账户的余额
- private Integer balance;
- //转账操作
- public void transfer(TansferAccount target, Integer transferMoney){
- boolean isThisLock = thisLock.tryLock();
- if(isThisLock){
- try{
- boolean isTargetLock = targetLock.tryLock();
- if(isTargetLock){
- try{
- if(this.balance >= transferMoney){
- this.balance -= transferMoney;
- target.balance += transferMoney;
- }
- }finally{
- targetLock.unlock
- }
- }
- }finally{
- thisLock.unlock();
- }
- }
- }
- }
例外,Lock下面有一个ReentrantLock,而ReentrantLock支持公平锁和非公平锁。
在使用ReentrantLock的时候, ReentrantLock中有两个构造函数, 一个是无参构造函数, 一个是传入fair参数的构造函数。 fair参数代表的是锁的公平策略, 如果传入true就表示需要构造一个公平锁, 反之则表示要构造一个非公平锁。如下代码片段所示。
- //无参构造函数: 默认非公平锁
- public ReentrantLock() {
- sync = new NonfairSync();
- }
- //根据公平策略参数创建锁
- public ReentrantLock(boolean fair){
- sync = fair ? new FairSync() : new NonfairSync();
- }
锁的实现在本质上都对应着一个入口等待队列, 如果一个线程没有获得锁, 就会进入等待队列, 当有线程释放锁的时候, 就需要从等待队列中唤醒一个等待的线程。 如果是公平锁, 唤醒的策略就是谁等待的时间长, 就唤醒谁, 很公平; 如果是非公平锁, 则不提供这个公平保证, 有可能等待时间短的线程反而先被唤醒。 而Lock是支持公平锁的,synchronized不支持公平锁。
最后,值得注意的是,在使用Lock加锁时,一定要在finally{}代码块中释放锁,例如,下面的代码片段所示。
- try{
- lock.lock();
- }finally{
- lock.unlock();
- }
注:其他synchronized和Lock的详细说明,小伙伴们自行查阅即可。
Java:既然有了synchronized,为什么还要提供Lock?的更多相关文章
- 【高并发】面试官:Java中提供了synchronized,为什么还要提供Lock呢?
写在前面 在Java中提供了synchronized关键字来保证只有一个线程能够访问同步代码块.既然已经提供了synchronized关键字,那为何在Java的SDK包中,还会提供Lock接口呢?这是 ...
- Java有了synchronized,为什么还要提供Lock
摘要:在Java中提供了synchronized关键字来保证只有一个线程能够访问同步代码块.既然已经提供了synchronized关键字,那为何在Java的SDK包中,还会提供Lock接口呢? 本文分 ...
- Java并发编程:synchronized
Java并发编程:synchronized 虽然多线程编程极大地提高了效率,但是也会带来一定的隐患.比如说两个线程同时往一个数据库表中插入不重复的数据,就可能会导致数据库中插入了相同的数据.今天我们就 ...
- Java SE中的Synchronized
1 引言 在多线程并发的编程中Synchronized一直是元老级的角色,很多人会称呼它为重量级锁,但是随着Java SE1.6对Synchronized进行了各种优化以后,有些情况下它并不那么重了. ...
- Java多线程之synchronized及其优化
Synchronized和同步阻塞synchronized是jvm提供的同步和锁机制,与之对应的是jdk层面的J.U.C提供的基于AbstractQueuedSynchronizer的并发组件.syn ...
- 【转】Java并发编程:synchronized
一.什么时候会出现线程安全问题? 在单线程中不会出现线程安全问题,而在多线程编程中,有可能会出现同时访问同一个资源的情况,这种资源可以是各种类型的资源:一个变量.一个对象.一个文件.一个数据库表等,而 ...
- JAVA多线程之Synchronized、wait、notify实例讲解
一.Synchronized synchronized中文解释是同步,那么什么是同步呢,解释就是程序中用于控制不同线程间操作发生相对顺序的机制,通俗来讲就是2点,第一要有多线程,第二当多个线程同时竞争 ...
- Java多线程简析——Synchronized(同步锁)、Lock以及线程池
Java多线程 Java中,可运行的程序都是有一个或多个进程组成.进程则是由多个线程组成的.最简单的一个进程,会包括mian线程以及GC线程. 线程的状态 线程状态由以下一张网上图片来说明: 在图中, ...
- 4、Java并发编程:synchronized
Java并发编程:synchronized 虽然多线程编程极大地提高了效率,但是也会带来一定的隐患.比如说两个线程同时往一个数据库表中插入不重复的数据,就可能会导致数据库中插入了相同的数据.今天我们就 ...
随机推荐
- ftp: connect: No route to host 解决方案
实验环境: centos7 x2 server-vsftp:192.168.1.32 client:192.168.95 客户端测试访问 ftp服务器 报错:ftp: connect: No rout ...
- 完整实现-通过DelayQueue实现延时任务
实现延时任务有很多的方法,网上关于延时任务的实现的文章已经不少了.比如:实现延时任务的10种方法等等.但是这些文章基本上都是将方法大概的列举一下,给出部分示例代码,对于有经验的老程序员可能一看就知道该 ...
- Mybatis简单入门--插入数据
1. 开发环境 IDE:IDEA 构建工具:maven4.0.0 MySQL版本:8.0.11. 记得创建好数据库 Mybatis版本:3.5.7 MySQL不同版本的注意事项 驱动类driver-c ...
- [网鼎杯 2018]Comment-1|SQL注入|二次注入
1.打开之后只有一个留言页面,很自然的就想到了二次注入得问题,顺带查看了下源代码信息,并没有什么提示,显示界面如下: 2.那先扫描一下目录,同时随便留言一个测试以下,但是显示需要登录,账户.密码给出了 ...
- Windows 系统 PostgreSQL 手工安装配置方法
自从2020年底开始接触 PostgreSQL 以来就喜欢上了这个数据库,个人感觉比 MySQL 好用,多表联合查询性能好很多,同时也不存在 SQLServer 的版权授权费用问题.搭配 .NET 开 ...
- 华为云计算灾备产品BCManager 及eBackup的组网方式
BCManager的作用 OceanStor BCManager是面向企业数据中心存储容灾业务的管理软件,实现容灾.双活.两地三中心等容灾环境的管理,具备多种数据库应用与虚拟化环境的容灾管理功能,简单 ...
- ESP8266 RTOS SDK开发
ESP8266 RTOS SDK开发 目录 ESP8266 RTOS SDK开发 一.源码RTOS SDK包的下载和编译 二.固件烧录 1.管脚定义 三.程序例程 ## 1.PWM设置 连接MQTT ...
- kingbaseES R3 集群修改data路径测试案例
案例说明: 默认KingbaseES R3集群部署后,数据存储目录(data)在/home/kingbase下,部署时不能更改:本案例是在部署完成后,迁移data目录到其他指定的存储位置. 数据库版本 ...
- KingbaseFlySync 需要对外开放的端口
Oracle到kes双轨灾备场景 源:Oracle rac 11g 目标端:kes v8r6c4b21 源.目标.管控服务器IP 需要开放端口 为什么源和目标需要互相开放数据库端口:因为在双轨运行的方 ...
- springboot拦截器总结
Springboot 拦截器总结 拦截器大体分为两类 : handlerInterceptor 和 methodInterceptor 而methodInterceptor 又有XML 配置方法 和A ...