一篇文章彻底搞懂面试中常被问的各种“锁”

 更新时间:2019年05月07日 10:13:34   作者:深夜里的程序猿  
这篇文章主要给大家介绍了关于面试中常被问的各种“锁”的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧

前言

锁,顾名思义就是锁住一些资源,当只有我们拿到钥匙的时候,才能操作锁住的资源。在我们的Java,数据库,还有一些分布式的环境中,总是充斥着各种各样的锁让人头疼,例如“公平锁”、“自旋锁”、“读写锁”、“分布式锁”等等。

其实真实的情况是,锁并没有那么多,很多概念只是从不同的功能特性,设计,以及锁的状态这些不同的侧重点来说明的,因此我们可以根据不同的分类来搞明白为什么会有这些“锁”?坐稳扶好了,准备开车。

正文

“公平锁”与“非公平锁”

  • 公平锁:指线程在等待获取同一个锁的时候,是严格按照申请锁的时间顺序来进行的,这就意味着在程序正常运作的时候,不会有线程执行不到,而被“饿死”,但是也需要额外的机制来维护这种顺序,所以效率相对于非公平锁会差点。
  • 非公平锁:概念跟“公平锁”恰恰相反,随机线程获取锁,相率相对高。
new ReentrantLock(); //默认非公平锁
new ReentrantLock(true); //公平锁

“重入锁(递归锁)”与“不可重入锁(自旋锁)”

这里要注意了,重入/递归,不可重入/自旋,虽然名字不同,但是确实是同一种锁,只是从锁的表现跟实现方式的角度来命名而已。

重入锁:当一个线程获取了A锁以后,若后续方法运行被A锁锁住的话,当前线程也是可以直接进入的。

public class Demo {
 private Lock lockA;
 
 public Demo(Lock Lock) {
  this.lockA = lock;
 }
 
 public void methodA() {
  lockA.lock();
  methodB();
  lockA.unlock();
 }
 
 public void methodB() {
  lockA.lock();
  //dosm
  lockA.unlock();
 }
 }

当我们运行methodA()的时候,线程获取了lockA,然后调用methodB()的时候发现也需要lockA,由于这是一个可重入锁,所以当前线程也是可以直接进入的。在java中,synchronized跟ReetrantLock都是可重入锁。 

不可重入锁:以上面的代码实例来说明,就是methodA进入methodB的时候不能直接获取锁,必须先调用unLock释放锁。才能执行下去,那实现不可重入锁有什么方式呢?那就是自旋,所以会有一个小名叫做自旋锁。

public class SpinLock {

 private AtomicReference<Thread> sign =new AtomicReference<>();

 public void lock(){
  Thread current = Thread.currentThread();
  while(!sign .compareAndSet(null, current)){
  }
 }

 public void unlock (){
  Thread current = Thread.currentThread();
  sign .compareAndSet(current, null);
 }
}

 “悲观锁”与“乐观锁”

这两种锁呢,其实是一个很宏观的分类,它不是一种具体的锁,而是泛指看待并发的程度。

悲观锁:有一个“悲观”的心态,既每次取数据的时候,都会认为该数据会被修改,所以必须加一把锁才安心。

乐观锁:乐观的孩子,认为同一个数据不会发生并发操作的行为,所以取的时候不会加锁,只有在更新的时候,会通过例如版本号之类的来判断是否数据被修改了。

Java中各种锁其实都是悲观锁的实现,既操作的数据的都会被获取锁的线程锁住,而乐观锁的话,一般是通过cas(compare and swap)的思想来实现,例如一些原子类AtomicInteger使用自旋来原子更新。

“共享锁”与“排他锁”

这两种锁的概念比较多的出现在数据库的事务当中。

共享锁:也称读锁或S锁。如果事务对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排它锁。获准共享锁的事务只能读数据,不能修改数据。在java中的ReetrantReadWriteLock()也是如此。

排它锁:也称独占锁、写锁或X锁。如果事务对数据A加上排它锁后,则其他事务不能再对A加任何类型的锁。获得排它锁的事务即能读数据又能修改数据。

分布式锁

我们上面聊的这些锁,都是在单个程序上面的不同线程之间来实现的,那么当我们的不同程序需要去竞争同一块资源的时候,这就需要分布式锁了,我们可以通过redis、zookeeper等中间件来实现分布式锁。

 对于锁来说,其实还有偏向锁,轻量级锁等,但是这里涉及到的内容就比较多,这里就不在展开篇幅介绍了,有兴趣的同学可自行研究,如果你能搞懂上面介绍的这些锁,那基本上在绝大部分的公司关于“锁”的问题都可以迎刃而解。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对脚本之家的支持。

相关文章

  • Golang Protocol Buffer案例详解

    Golang Protocol Buffer案例详解

    这篇文章主要介绍了Golang Protocol Buffer案例详解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-08-08
  • 启动Tomcat时出现大量乱码的解决方法

    启动Tomcat时出现大量乱码的解决方法

    今天给大家带来的是关于Java的相关知识,文章围绕着启动Tomcat时出现大量乱码的解决方法展开,文中有非常详细的介绍及图文示例,需要的朋友可以参考下
    2021-06-06
  • SpringBoot 在项目启动之后执行自定义方法的两种方式小结

    SpringBoot 在项目启动之后执行自定义方法的两种方式小结

    这篇文章主要介绍了SpringBoot 在项目启动之后执行自定义方法的两种方式小结,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • SpringBoot 在测试时如何指定包的扫描范围

    SpringBoot 在测试时如何指定包的扫描范围

    这篇文章主要介绍了SpringBoot 在测试时如何指定包的扫描范围,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • java反射使用示例分享

    java反射使用示例分享

    这篇文章主要介绍了java反射使用示例,代码很简单,需要的朋友可以参考下
    2014-02-02
  • Java线程中的Thread.yield()详细解析

    Java线程中的Thread.yield()详细解析

    这篇文章主要介绍了Java线程中的Thread.yield()详细解析,yield()让当前线程从运行状态 转为 就绪状态,以允许具有相同优先级的其他线程获得运行机会,需要的朋友可以参考下
    2023-11-11
  • 6种SpringBoot解决跨域请求的方法整理

    6种SpringBoot解决跨域请求的方法整理

    跨域资源共享是一种标准机制,允许服务器声明哪些源可以访问其资源,在SpringBoot应用中,有多种方式可以解决跨域问题,本文主要介绍了6种常见的解决方案,大家可以根据需求自行选择
    2025-04-04
  • java  Iterator接口和LIstIterator接口分析

    java Iterator接口和LIstIterator接口分析

    这篇文章主要介绍了java Iterator接口和LIstIterator接口分析的相关资料,需要的朋友可以参考下
    2017-05-05
  • Java中的ThreadLocal线程池原理

    Java中的ThreadLocal线程池原理

    这篇文章主要介绍了Java中的ThreadLocal线程池原理,ThreadLocal提供了线程的局部变量(或本地变量),它可以保证访问到的变量属于当前线程,每个访问这种变量的线程(通过它的get或set方法)都有自己的、独立初始化的变量副本,需要的朋友可以参考下
    2023-11-11
  • JAVA8如何妙用Optional解决NPE问题详解

    JAVA8如何妙用Optional解决NPE问题详解

    在Java中,null代表一个不存在的对象,如果对它进行操作就会抛出java.lang.NullPointerException异常,下面这篇文章主要给大家介绍了关于JAVA8如何妙用Optional解决NPE问题的相关资料,需要的朋友可以参考下
    2018-06-06

最新评论