浅析synchronized和Lock
1.写在前面
在最近的一次面试中,被面试官问到了synchronized已经可以保证一个线程能够同步访问代码块,为什么还要单独的提供Lock接口呢?今天,我们就来一起探讨下这个问题。
2.死锁问题
在谈论synchronized和Lock之前,我们先来看一下死锁的问题。如果要发生死锁,则必须存在以下四个必要条件,缺一不可。
- 互斥条件:
在一段时间内某资源仅为一个线程所占有。此时若有其他线程请求该资源,则请求线程只能等待。 - 不可剥夺条件:
线程所获得的资源在未使用完毕之前,不能被其他线程强行夺走,即只能由获得该资源的线程自己来释放(只能是主动释放)。 - 请求与保持条件:
线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他线程占有,此时请求线程被阻塞,但对自己已获得的资源保持不放。 - 循环等待条件:
在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一个进程所深情地资源。
3.synchronize的局限性
当我们使用synchronize关键字发生了死锁的时候,synchronize关键是没有办法破坏“不可剥夺”条件的。这是因为如果synchronize申请资源时候,没有申请到,线程直接进入阻塞状态,从而什么都做不了,也相应的无法释放资源。
4.锁问题的解决
了解到synchronize的局限性,我们在设计一把锁的时候就应该考虑到下面这些特性。
特性 | 描述 |
---|---|
尝试性的非阻塞的获取锁 | 当前线程尝试的获取锁,如果这一时段没有被q其他线程获取,则成功的获取锁,否则直接返回false |
能被中断的获取锁 | 如果阻塞状态的线程能够响应中断信号, 也就是说当我们给阻塞的线程发送中断信号的时候, 能够唤醒它, 那它就有机会释放曾经持有的锁A。这样就破坏了不可剥夺条件了。 |
超时获取锁 | 如果线程在一段时间之内没有获取到锁, 不是进入阻塞状态, 而是返回一个错误, 那这个线程也有机会释放曾经持有的锁。这样也能破坏不可剥夺条件。 |
5.Lock锁的特性
5.1尝试性非阻塞地获取锁(tryLock)
- tryLock()方法
tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待 - tryLock(long time, TimeUnit unit)方法
tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
这样看来,Lock就已经解决了对于不可剥夺条件的破坏
5.2能被中断的获取锁(lockInterruptibly()throws InterruptedException)
与synchronized不同,获取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁会被释放。
- 当前线程获取锁之前(并未参与获取锁)被其他线程标记interrupt中断,当调用此方法时直接抛出中断异常。
- 当前线程获取锁,并且锁被其他线程持有,则一直阻塞,此时其他线程来中断此线程,则会抛出中断异常。
5.3超时获取锁(tryLock(long time,TimeUtil unit)throws InterruptedException)
在指定的时间内能够获取锁,超出时间仍热无法获取,则返回
- 当前线程在指定时间内获取了锁。
- 当前线程在指定时间内被中断,锁被释放。
- 当前线程在超出指定的时间,则直接返回false。
6.总结
- Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将 unLock()放到finally{} 中;
- synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
- Lock可以让等待锁的线程响应中断,线程可以中断去干别的事务,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
- synchronize是一把悲观锁,Lock是一把乐观锁,悲观锁和乐观锁在并发量低的时候,性能差不多,但是在并发量高的时候, 乐观锁的性能远远优于悲观锁。也就是说并发量大的时候Lock的性能要远远好于synchronize的。
- Lock锁可以知道是否获取锁成功,而对于synchronize来说 这是不可能的
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。