千家信息网

Java中提供synchronized后还要提供Lock的原因是什么

发表于:2024-10-18 作者:千家信息网编辑
千家信息网最后更新 2024年10月18日,这篇文章主要介绍"Java中提供synchronized后还要提供Lock的原因是什么",在日常操作中,相信很多人在Java中提供synchronized后还要提供Lock的原因是什么问题上存在疑惑,
千家信息网最后更新 2024年10月18日Java中提供synchronized后还要提供Lock的原因是什么

这篇文章主要介绍"Java中提供synchronized后还要提供Lock的原因是什么",在日常操作中,相信很多人在Java中提供synchronized后还要提供Lock的原因是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"Java中提供synchronized后还要提供Lock的原因是什么"的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

摘要: 在Java中提供了synchronized关键字来保证只有一个线程能够访问同步代码块。既然已经提供了synchronized关键字,那为何在Java的SDK包中,还会提供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接口提供的三个方法,

如下所示:

// 支持中断的APIvoid lockInterruptibly() throws InterruptedException;// 支持超时的APIboolean tryLock(long time, TimeUnit unit) throws InterruptedException;// 支持非阻塞获取锁的APIboolean 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的原因是什么"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!

线程 资源 条件 问题 阻塞 方法 死锁 关键 支持 代码 关键字 接口 时间 状态 也就是 时候 进程 设计 原因 也就是说 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 泰拉瑞亚手游服务器教程 软件开发u盘哪里有卖的 gin 小程序 数据库 易安卓怎么建立数据库 河北区正规软件开发专业服务 聊城市战狼网络技术有限公司 家庭影音服务器 树霉派 北邮网络技术中心怎么样 服务器乱码 江苏电商软件开发收费标准 双路服务器电源管理器 电脑服务器上三角形感叹号亮红灯 汽车电子网络安全标准 项目部网络安全管理制度 长沙软件开发项目需要加班吗 金融科技和互联网金融有啥区别 银行网络安全工作总结报告 软件开发的需求怎么写 饥荒独立服务器 设置管理员 方舟生存进化服务器作弊管理翻译 登陆亚马逊需要买什么服务器 网络安全需要我们共同守护 家庭影音服务器 树霉派 四川仁贤互联网科技有限公司 福建日报社网络安全项目 加大网络安全投入措施 数据库怎么定义int类型 管家婆数据库表字段 安卓软件开发的ppt模板 如何用电脑玩手游我的世界服务器
0