Android面试指南(三)———(1)

ThreadLocal的理解

面试官:小伙子,听说你看过ThreadLocal源码?(万字图文深度解析ThreadLocal)

线程隔离,数据不交叉

  • ThreadLocalMap,每个thread都存在一个变量ThreadLocalMap threadLocals
  • threadLocalMap中存在Entry,同ThreadLocal之间为弱引用关系
  • ThreadLocalMap中key为ThreadLocal的弱引用,value为Entry,内部为一个object对象
  • table默认大小为16,存在初始容量(16)和阈值(16*2/3)
  • 在ThreadLocal中使用get()和set()方法初始化threadLocals
  • get、set、remove方法将key==null的数据清除
  • table是环形数组

线性探测法避免哈希冲突,增量查找没有被占用的地方

通过hashcode计算索引位置,如果key值相同,则替换,不同就nextIndex,继续判断,直到插入数据

ThreadLocal就是管理每个线程中的ThreadLocalMap,所以线程隔离了。

ThreadLocalMap的理解

新建ThreadLcoal的时候,创建一个ThreadLocalMap对象,计算hash的时候使用0x61c88647这个值,他是黄金分割数,导致计算出来的hash值比较均匀,这样回大大减少hash冲突,内部在采用线性探测法解决冲突 set:

  1. 根据key计算出数组索引值
  2. 遍历该索引值的链表,如果为空,直接将value赋值,如果key相等,直接更新value,如果key不相等,使用线性探测法再次检测。
ThreadLocal使用弱引用的原因

key使用了弱引用,如果key使用强引用,那么当ThreadLocal的对象被回收了,但ThreadLocalMap还持有ThreadLocal的强引用,回导致ThreadLocal不会被回收,导致内存泄漏

ThreadLocal的内存泄漏
  • 避免使用static修饰ThreadLocal:延长生命周期,可能造成内存泄漏
  • ThreadLocal弱引用被gc回收后,则key为null,object对象没有被回收,只有当再次调用set,get,remove方法的时候才会清楚key为null的对象
ThreadLocalMap清理过期key的方式
  1. 探测式清理 本该放在4的位置上的值,放到了7的位置上,当5过时后,将7的数据挪到5的位置上
  2. 启发式清理 遍历数组,清理数据
ConcurrentHashMap和HashMap的区别

jdk 1.7 ReentrantLock+segments + hashEntry(不可变)

  • 线程安全,分段线程锁,hashtable是整段锁,所以性能有所提高
  • 默认分配16个锁,比Hashtable效率高16倍
  • hashEnty是final的,不能被修改,只要被修改,该节点之前的链就要重新创建,采用头插插入,所以顺序反转
  • 获取size,因为是多线程访问,所以size会获取三遍,如果前后两个相等就返回,假设不相等,就将Segment加锁后计算。

jdk 1.8 : synchronized +node+volatile+红黑树

put:

  1. 根据key的hash值算出Node数组的相应位置
  2. 如果该Node不为空,且当前该节点不处于移动状态,则对节点加synchronized锁,进行遍历节点插入操作
  3. 如果是红黑树节点,向红黑树插入操作
  4. 如果大于8个,拓展为红黑树

get:

  1. 计算hash值,定位到该table索引位置,如果是首节点符合就返回
  2. 如果遇到扩容的时候,会调用标志正在扩容节点ForwardingNode的find方法,通知在新表中查找该节点,匹配就返回
  3. 以上都不符合的话,就往下遍历节点,匹配就返回,否则最后就返回null

1.7和1.8的区别:

  1. 1.7:ReentrantLock+segments + hashEntry(不可变)

1.8:synchronized +node+volatile+红黑树

  1. 1.8的锁的粒度更低,锁的是一个链表(table[i]),而1.7锁的是一个小的hashmap(segement)

  2. ReentrantLock性能比synchronized差

扩容:

1.7下进行小HashMap(segement)扩容操作

1.8下使用synchrozied节点加锁,所以可以通过多个线程扩容处理。一个线程创建新的ConcurrentHashMap,并设置大小,多个线程将旧的内容添加到新的map中,如果添加过的内容就会设置标记,其他线程就不会处理

为什么只有hashmap可以存储null值和null键

因为hashmap是线程不安全的,而在其他中都是线程安全的,在多线程访问时,无法判断key为null是没有找到,还是key为null

常见锁

锁的分类
  1. 公平锁/非公平锁
  • 公平锁:多个线程按照申请锁的顺序获取锁。
  • 非公平锁:多个线程申请锁并不是按照顺序获取锁,有可能先申请后获取锁。(Synchronized)

ReentrantLock默认是非公平锁,通过构造传参可设置为公平锁。非公平锁的优点在于吞吐量比公平锁大

  1. 可重入锁:又名递归锁,指在外层方法获取锁以后,在进入内层方法也会自动获取锁。

synchronized void setA() throws Exception(){
Thread.sleep(1000);
setB();
}

synchronized void setB() throws Exception(){
Thread.sleep(1000);
}

如果不是可重入锁,那么setB方法不会被当前线程执行,容易造成死锁

synchronized是可重入锁

  1. 独享锁/共享锁
  • 独享锁:一个锁一次只能被一个线程所持有(ReentrantLock,synchronized)
  • 共享锁:一个锁被多个线程所持有。(ReadWriteLock)
  1. 互斥锁/读写锁 上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。
    互斥锁在Java中的具体实现就是ReentrantLock
    读写锁在Java中的具体实现就是ReadWriteLock

  2. 乐观锁/悲观锁

  • 悲观锁:对同一数据的并发操作,一定会发生修改的。(利用各种锁实现)
  • 乐观锁:对同一数据的并发操作,一定不会发生修改的。(无锁编程,CAS算法,自旋实现原子操作的更新)
  1. 分段锁
    是一种锁的设计,并不是具体的锁,在1.7版本的ConcurrentHashMap中,使用分段锁设计,该分段锁又称为Segment,map中每一个链表由ReentrantLock修饰

  2. 偏向锁/轻量级锁/重量级锁 这三种锁是描述synchronized的三种状态。

  • 偏向锁:一段同步代码一直被一个线程访问,那么会自动获取锁,降低获取锁的代价
  • 轻量级锁:当锁是偏向锁的时候,被另一个线程访问,偏向锁会升级为轻量级锁,其他线程通过自旋的方式获取锁,不会阻塞,提高性能
  • 重量级锁:在轻量级锁的基础上,自旋达到上限就会阻塞,升级为重量级锁,会让其他线程进入阻塞,影响性能。

锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后无法降为偏向锁,这种升级无法降级的策略目的就是为了提高获得锁和释放锁的效率。

  1. 自旋锁
    获取锁的过程中,不会立即阻塞,会采用循环的方式获取锁,减少线程切换上下文的消耗,缺点是循环会消耗cpu
java中常用锁的类型
  1. synchronized:非公平,悲观,独享,互斥,可重入,重量级锁
  2. ReentrantLock:默认非公平(可公平),悲观,独享,互斥,可重入,重量级锁

CAS,全称为Compare-And-Swap,是一条CPU的原子指令,其作用是让CPU比较后原子地更新某个位置的值,实现方式是基于硬件平台的汇编指令,就是说CAS是靠硬件实现的,JVM 只是封装了汇编调用,那些AtomicInteger类便是使用了这些封装后的接口。

synchronized和volatile

简述synchronized的原理

可见性:表示A修改的值对于B执行时可以看见A修改后的值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值