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加锁是无法主动释放锁的,这就会涉及到死锁的问题 。
死锁问题如果要发生死锁,则必须存在以下四个必要条件,四者缺一不可 。
Java:既然有了synchronized,为什么还要提供Lock?

文章插图
  • 互斥条件
在一段时间内某资源仅为一个线程所占有 。此时若有其他线程请求该资源,则请求线程只能等待 。
  • 不可剥夺条件
线程所获得的资源在未使用完毕之前 , 不能被其他线程强行夺走,即只能由获得该资源的线程自己来释放(只能是主动释放) 。
  • 请求与保持条件
线程已经保持了至少一个资源,但又提出了新的资源请求 , 而该资源已被其他线程占有,此时请求线程被阻塞,但对自己已获得的资源保持不放 。
  • 循环等待条件
在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源 , P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请 , 也就是前一个进程占有后一个进程所深情地资源 。
synchronized的局限性如果我们的程序使用synchronized关键字发生了死锁时,synchronized关键是是无法破坏“不可剥夺”这个死锁的条件的 。这是因为synchronized申请资源的时候,如果申请不到 ,  线程直接进入阻塞状态了,而线程进入阻塞状态,啥都干不了 ,  也释放不了线程已经占有的资源 。
然而,在大部分场景下,我们都是希望“不可剥夺”这个条件能够被破坏 。也就是说对于“不可剥夺”这个条件 , 占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源, 这样不可剥夺这个条件就破坏掉了 。
如果我们自己重新设计锁来解决synchronized的问题,我们该如何设计呢?
解决问题了解了synchronized的局限性之后 , 如果是让我们自己实现一把同步锁,我们该如何设计呢?也就是说,我们在设计锁的时候,要如何解决synchronized的局限性问题呢?这里,我觉得可以从三个方面来思考这个问题 。
Java:既然有了synchronized,为什么还要提供Lock?

文章插图
(1)能够响应中断 。synchronized的问题是,持有锁A后,如果尝试获取锁B失败,那么线程就进入阻塞状态,一旦发生死锁, 就没有任何机会来唤醒阻塞的线程 。但如果阻塞状态的线程能够响应中断信号,也就是说当我们给阻塞的线程发送中断信号的时候, 能够唤醒它,那它就有机会释放曾经持有的锁A 。这样就破坏了不可剥夺条件了 。
(2)支持超时 。如果线程在一段时间之内没有获取到锁 ,  不是进入阻塞状态,而是返回一个错误,那这个线程也有机会释放曾经持有的锁 。这样也能破坏不可剥夺条件 。
(3)非阻塞地获取锁 。如果尝试获取锁失败, 并不进入阻塞状态,而是直接返回, 那这个线程也有机会释放曾经持有的锁 。这样也能破坏不可剥夺条件 。
体现在Lock接口上,就是Lock接口提供的三个方法 , 如下所示 。
// 支持中断的APIvoid lockInterruptibly() throws InterruptedException;// 支持超时的APIboolean tryLock(long time, TimeUnit unit) throws InterruptedException;// 支持非阻塞获取锁的APIboolean tryLock();

推荐阅读