面试扩展题

1、请简述一下synchronize锁的升级过程

(节选Java synchronized锁升级过程简述(面试可用)_memory_cood的博客-CSDN博客

大概的升级过程是:无锁-->偏向锁-->轻量级锁-->锁自旋-->重量级锁

偏向锁:

偏向锁,就是在锁对象的对象头中存放一个ThreadId字段,如果这个字段是空的话,第一次获取锁的时候,就将自身的ThreadId写入到锁的ThreadId字段中,将锁头内的是否偏向锁的状态位置设为1,这样下次获取锁的时候,直接检查ThreadId是否和自身线程Id一致,如果一致,则认为当前线程已经获取了锁,因此不需再次获取锁,略过了轻量级锁和重量级锁的加锁阶段。提高了效率。(摘抄的哈哈)

轻量级锁:

轻量级锁,偏向锁是单线程下的锁优化,这个就说多线程下的锁优化了,当有多个线程竞争同一个临界资源,这个时候偏向锁就会被撤(这个步骤也是十分消耗资源的),然后升级为轻量级锁,这个也是一个基于CAS的乐观锁。

锁自旋:

 锁自旋,什么是锁自旋呢,很简单就是线程自己做一些无用功,避免线程被挂起阻塞 ,它自己在哪里做一些空任务,然后去竞争锁,避免被挂起阻塞(阻塞和唤醒是又是十分消耗性能的行为,这边涉及到用户态和核心态的操作系统问题,一般我们操作的都是用户态,但是线程的挂起阻塞是需要从用户态切换到核心态,同样,线程唤醒也一样,这个步骤会造成巨大的性能消耗,能避免尽量避免)。当然,锁自旋也是会消耗一定的CPU的

重量级锁:

重量级锁,也就是再一次的锁升级。这个时候线程就是进行锁自旋也不到锁,因为锁自旋也是需要消耗一定资源的,所以它不可能一直自旋自旋失败了,那么就进行锁膨胀,升级为重量级锁。

2、线程池核心参数有哪些?分别是什么意思?

一、corePoolSize           线程池核心线程大小

二、maximumPoolSize  线程池最大线程数量

三、keepAliveTime        多余的空闲线程存活时间

四、unit                           空闲线程存活时间单位

五、workQueue              工作队列

六、threadFactory          线程工厂

七、handler                     拒接策略

3、线程池有哪些拒绝策略

一、AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。(默认使用这种)

二、DiscardPolicy:丢弃任务,但是不抛出异常。

三、DiscardOldPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程),也就是说当任务被拒绝添加时,会抛弃任务队列中最旧的任务也就是最先加入队列的任务,再把这个新任务从队尾添加进去,等待执行。

四、CallerRunsPolicy:谁调用,谁处理。由调用线程(即提交任务给线程池的线程)处理该任务,如果线程池已经被shutdown则直接丢弃。 

4、什么是CAS?CAS存在哪些问题?

CAS:是compare and swap 的缩写,即我们平时说的比较交换。CAS 是一种基于锁的操作,而且是乐观锁。

CAS存在的问题:

        1:CAS容易做出ABA 问题。ABA问题:一个线程a将数值改成了b,接着又改为了a,此时CAS认为线程a线程没有变化过,但是现实中是已经改变了,而这个问题的解决方案就是可以使用版本号进行标识,每操作一次version加1,。在java5中,已经提供了AtomicStampedReference 类解决此问题。

        2:不能保证代码块的原子性。CAS机制所保证的知识一变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用synchronize了

        3:CAS造成CPU利用率增加。之前说过了CAS里面是一个循环判断的过程,如果线程一致没有获取到状态,CPU资源会一直被占用,就会导致CPU飙高问题。引申出解决CPU飙高问题请参考:jvm如何排查生产环境cpu飙高的问题_gblfy的博客-CSDN博客

 5、简述ConcurrentHashMap的put/get方法执行流程

put:

        最最最一开始得检查桶是否进行初始化,然后确定判断所放桶中是否有元素,没有元素的话,那么就用CAS的方式添加元素,当然有可能失败,有可能其他线程已经抢占先添加,这个过程在一个死循环里,失败了再从头来一次,判断桶是否初始化,桶内是否有元素等等,如果此时桶内有元素了,那么判断桶内第一元素的hash值是否为MOVED,说明正在扩容,那么此线程就会帮助扩容,如果不是的话,那么就对一个元素synchronized上锁,上锁之后还要对桶内第一个元素判断是否发生了改变,发生变化了就再从头再来。我对再次判断桶内第一个元素的理解:如果在加锁的时候,此时有可能其他线程删除了第一个元素,那么就产生了错误。经历了重重困难终于能插入数据了,此时就和HashMap有点像了,如果第一元素是链表节点,那么遍历链表查询是否key相等,若查到把value更改为我们要put的value值,没找到的话新建节点插入到链表尾部。若第一个节点是树节点,那么就以树的方式插入。

get:

        检查桶的长度是否为空,或者根据key得到具体哪个桶中没有数据,那么直接返回null,如果桶不为空且要查询的桶中有元素,那么桶中第一个元素的hash值如果大于0说明为链表节点,那么遍历链表查询即可,小于0说明是树节点或者正在扩容,那么调用Node子类的find函数进行查询。

TIP:hash值大于0说明是链表节点,小于0说明在在迁移或者是树

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值