Synchronized和ReentrantLock怎么回答?

提到多线程,肯定离不开线程的同步方式,线程的同步方式有三种
一、同步方法块
二、同步方法
三、锁
接下来面试管肯定矛头直指synchronized和Lock的区别?
什么?你说你不知道?那你回家等通知吧

1、乐观锁与悲观锁

我觉得很有必要讲一下什么是乐观锁,什么是悲观锁

  • 所谓的悲观锁,就是我认为操作是不安全的,所以每次操作我都要加锁保证安全
  • 所谓的乐观锁,就是我认为操作时安全的,只有在真正操作的时候,我才去验证这个操作的安全性

(1)乐观锁

CAS(Compare And Swap),就是一种乐观锁的实现

1、那么CAS时怎么实现线程安全的?
在这里插入图片描述

  • 拿数据库做例子就是,我第一次从数据库查询select userid username from user where age=26;,我查询到了一条数据,当我需要吧这条记录的username去更改的时候,我需要先去看一下当前我已经拿到的数据是不是和数据库的一致,如果是一致的话,说明当前我获取到的是最新的值,允许更新。否则的话,说明当前的值被更改过了,你必须要拿最新的值,在那个基础之上去修改,当前的操作被拒绝,你要重新去获取最新的值
  • (这很好理解,就比如说你和你的同事A工作都要先从远程仓库拉取代码,当你们工作完了再把代码提交远程仓库。有一天你提前把自己的工作做完了,下班前把代码上传了,而同事A在提交的时候,直接强制提交了。第二天你来到公司你就会发现你的代码都不见了,这样明显是不合理的)

2、CAS会出现什么问题?

  • 提到CAS就不得不了解ABA的问题,什么是ABA?
小明小华
从数据读取到数据为A
更改同一条记录为B
又把记录从B改为A
比较发现当前的数据为A,更改

以上就是ABA的问题,就是小华这个人把记录从A改为B,又把B改为A。你说说现在记录是被修改过还是没被修改过?

解决方案:
     加一个标志位,像数据库那样,每次操作标志位+1,或者拿时间戳作对比

  • 循环时间长开销大的问题
    如果我CAS一直比较的数据都不是最新的数据,那么我们就要不断去自旋(不断重复的获取最新的值,相当于死循环)

(2)悲观锁

synchronized就是悲观锁的典型代表

1、对象的结构

  • 对象由3个部分组成:对象头、实例数据、对齐填充。而对象头里有一个部分称为Mard World,里面默认存储的是对象的hashcode,还有一些分代年龄,锁的标志位信息,而synchronized的锁实现也是通过更改对象头的信息来实现的。

2、synchronized的锁实现
在这里插入图片描述

在这里插入图片描述
通过对代码的编译和反编译,查看反编译后的代码,发现synchronized的实现是通过一个monitorenter来加锁的,通过monitorexit来解锁

步骤:

  1. 每个monitor维护着一个记录着拥有次数的计数器。未被拥有的monitor的该计数器为0,当一个线程获得monitor(执行monitorenter)后,该计数器自增变为 1 。

当同一个线程再次获得该monitor的时候,计数器再次自增;
当不同线程想要获得该monitor的时候,就会被阻塞。

  1. 当同一个线程释放 monitor(执行monitorexit指令)的时候,计数器再自减。当计数器为0的时候,monitor将被释放,其他线程便可以获得monitor。

3、锁优化
synchronized在大家的眼里看来,一直都是一种重量级锁的实现,但是在jdk1.6以后,对synchronized做了很多的优化。具体的优化请参考《深入理解Java虚拟机》

锁优化:

  1. 自旋锁与自适应自旋
    如果物理机器有一个以上的处理器或者处理器核心,能让两个或以上的线程同时并行执行,我们就可以让后面请求锁的那个线程“稍等一会”,但不放弃处理器的执行时间,看看持有锁的线程是否很快就会释放锁。为了让线程等待,我们只须让线程执行一个忙循环(自旋),这项技术就是所谓的自旋锁。
  2. 锁消除
  3. 锁粗化
  4. 锁升级过程
    我们在前面讲了,对象头里面有Mark World这样的一个区域,里面有2比特用于记录锁的状态。分为:未锁定、轻量级锁定、重量级锁定、偏向锁
    在这里插入图片描述
    (1)在代码即将进入同步块的时候,如果此同步对象没有被锁定(锁标志位为“01”状态),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间
    (2)然后,虚拟机将使用CAS操作尝试把对象的Mark Word更新为指向Lock Record的指针。如果这个更新动作成功了,即代表该线程拥有了这个对象的锁,并且对象Mark Word的锁标志位(Mark Word的最后两个比特)将转变为“00”,表示此对象处于轻量级锁定状态
    (3)如果这个更新操作失败了,那就意味着至少存在一条线程与当前线程竞争获取该对象的锁。虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是,说明当前线程已经拥有了这个对象的锁,那直接进入同步块继续执行就可以了,否则就说明这个锁对象已经被其他线程抢占了。如果出现两条以上的线程争用同一个锁的情况,那轻量级锁就不再有效,必须要膨胀为重量级锁,锁标志的状态值变为“10”,此时Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也必须进入阻塞状态。

在这里插入图片描述

2、synchronized

(1)一般用法

静态方法
成员方法
代码块

首先我们来看一下一些经常的用法,在什么时候锁住什么对象?

【一看就懂】Synchronized在什么时候获取什么锁?

(2)特性保证

  1. 有序性
    as-if-serial
    happen-before
  2. 可见性(与volatile对比)
    JMM
  3. 原子性
    确保同一时间只有一个线程能拿到锁
  4. 可重入性

3、Lock

4、Synchronized和Lock的区别

synchronizedlock
实现层面synchronized是关键字,是JVM层面的底层啥都帮我们做了而Lock是一个接口,是JDK层面的有丰富的API
锁的释放synchronized会自动释放锁而Lock必须手动释放锁
锁中断synchronized是不可中断的Lock可以中断也可以不中断
是否拿到锁synchronized不能知道有没有获取到锁通过Lock可以知道线程有没有拿到锁
作用域synchronized能锁住方法和代码块而Lock只能锁住代码块
公平锁synchronized是非公平锁ReentrantLock可以控制是否是公平锁

5、synchronized和ReentrantLock可重入实现

(1)synchronized

因为synchronized使用的是锁对象,当某个线程第一次持有锁后,会修改锁对象的mark word锁状态为偏向锁,偏向锁锁会在当前线程的栈帧中建立一个锁记录空间,mark word会将指针指向栈中的锁记录。当线程再次获取锁对象的时候,会检查mark word 中的指针是否指向当前线程的栈帧,如果是就直接获取锁,如果不是就需要竞争

(2)ReentrantLock

由于ReentrantLock是通过AQS来实现的,其使用了AQS的state状态值来表示线程获取该锁的可重入次数,默认情况下state为0表示当前锁没有被任何线程持有,当一个线程获取该锁时会尝试使用CAS设置state值为1,如果CAS设置成功则当前线程获取了该锁,然后记录该锁的持有者为当前线程,在该线程没有释放锁的情况下第二次获取该锁后,状态值被设置2,这就是可以重入次数,在释放锁的时候,需要通过CAS将状态值减1,直到状态值为0,表示当前线程释放该锁

AbstractQueuedSynchronizer的作用
抽象同步队列简称AQS,是实现同步器的基础组件,并发包中的锁都是基于其实现的,关键是先进先出的队列,state状态,并且定义了
ConditionObject ,拥有两种线程模式,独占模式和共享模式
AQS核心思想
如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制使用CLH队列实现的,即将暂时获取不到锁的线程加入到队列中
CLH(Craig,Landin,and
Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配,
并保持了上下节点,当前请求资源的线程
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值