浅析synchronized和Lock

浅析synchronized和Lock

1.写在前面

在最近的一次面试中,被面试官问到了synchronized已经能够保证一个线程可以同步访问代码块,为何还要单独的提供Lock接口呢?今天,咱们就来一块儿探讨下这个问题。

2.死锁问题

在谈论synchronized和Lock以前,咱们先来看一下死锁的问题。若是要发生死锁,则必须存在如下四个必要条件,缺一不可。

image-20200918125929918

  • 互斥条件:
    在一段时间内某资源仅为一个线程所占有。此时如有其余线程请求该资源,则请求线程只能等待。
  • 不可剥夺条件:
    线程所得到的资源在未使用完毕以前,不能被其余线程强行夺走,即只能由得到该资源的线程本身来释放(只能是主动释放)。
  • 请求与保持条件:
    线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其余线程占有,此时请求线程被阻塞,但对本身已得到的资源保持不放。
  • 循环等待条件:
    在发生死锁时必然存在一个进程等待队列{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来讲 这是不可能的
相关文章
相关标签/搜索