为什么HashMap数组的长度是2的指数次幂?
因为HashMap的底层是数组+链表+红黑树,在插入元素时,需要通过索引获得插入元素的位置,计算索引的方法是使用哈希函数,将元素的哈希值与数组长度取模,当数组长度是2的指数次幂时,取模操作相当于对元素的哈希值进行二进制位与运算(假如数组长度是4,那么索引最大是3),可以更快的计算索引。
动态扩容时,原数组中的位置会移到什么位置?
每次扩容都是增加原来长度的倍数,原数组中的元素位置要么保持不动,要么等于旧数组时的索引位置再加上旧数组长度。
HsahMap在并发情况下为什么会造成死循环?
HashMap在并发执行put操作时发生扩容,如果一个线程被阻塞,另一个线程扩容结束后头插法会使得链表顺序与之前相反,而新数组在由阻塞的线程继续进行扩容操作时会变成旧数组,在并发执行后就可能会出现节点丢失,产生环形链表等情况;jdk1.8中改为尾插法,避免了产生环形链表,但避免不了节点丢失。
JDK1.7 HashMap扩容使用头插法
扩容后顺序会改变
jdk1.7 ConcurrentHashMap
锁
java中的锁用来解决多线程并发情况下共享数据出现不一致的情况,对象头中存储锁标志位
无锁:没有对共享资源进行锁定(无竞争/乐观锁-CAS通过操作系统中一条指令来实现,能保证原子性)
偏向锁:先确定锁标志位,然后看mark word的前23bit记录着偏向的线程ID,如果当前有多个线程在争夺资源,则会变为轻量级锁
轻量级锁:线程在自己的虚拟机栈中开辟Lock Record的空间,线程通过CAS尝试获取锁,一旦获得将会使得对象头中Mark Word的前30个bit指向Lock Rocrd,Lock Record中存放mark word的副本,并指向该对象。其它线程将会自旋等待,如果自旋等待的线程超过1个,会变成重量级锁
重量级锁:需要通过Monitor对线程进行管控
sychrinized: 底层使用Monitor 监视器
无锁/乐观锁
CAS(compare and swap):new value代表想要将资源对象的状态值更新之后的值,old value表示之前获得的资源对象的值,若old value和当前资源对象的值不一样,则放弃swap操作,然后一直自旋(不断的重试CAS操作,配置循环次数防止死锁)
CAS的原子性由CPU指令集/底层指令架构支持。
vloatile关键字:保证数据的可见性,JVM将该变量从线程本地内存强行刷新到主内存中
禁止指令重排,保证放在vloatile关键字之前的语句不会在后面执行,保证放在后面的语句不会提前执行。(多线程中发生指令重排,会影响结果)
AQS:同步框架
AQS成员变量
vloatile int state:使用int是因为资源有两种模式分别为独占模式和共享模式,当为共享模式时,可能会有多个线程在共享资源,所以state表示当前资源线程占用的数量,对其的修改使用CAS原子操作;
如果一个线程在当前没有获取到资源,那它可以进入队列进行排队,队列是先进先出的双向链表。
Node head、 Node tail
Node成员变量
线程对象、节点在队列里的等待状态、前后指针等,等待状态有四个。
如何管理多线程同步状态?
两种使用场景:1、尝试获取锁,没有获取到立即返回 ——tryAquire() 调用该方法,成功获得锁时对想用资源进行操作,操作完释放,如果失败还想继续调用acquire()
2、获取锁,愿意进行队列等待,直到获取 acquire()
RenntrantLock,默认使用非公平锁
非公平锁:不按照锁的顺序分配,抢占式的,可能会有线程一直拿不到锁(在很多情况下,非公平锁的效率更高,当后请求的线程在前面休眠的线程恢复前拿到锁,提高并发性)
公平锁:锁的分配按照请求锁的顺序分配,比如说AQS的队列按照先进先出;如果当前线程已经获取锁,那么对state进行累加,可以继续使用锁,此外只有当前线程之前没有其他线程的情况下才会尝试获取锁,其它情况排队。