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说明在在迁移或者是树