- 动态代理 JDK动态代理:实现InvacationHandler,然后使用Proxy.newProxyInstance(Classloader, Class<?>, Handler) 调用。 Cglib动态代理:实现MethodInterceptor,CglibProxyFactory中将Enchanner.setCallBack(自定义的Interceptor)。 CglibProxyFactory.getProxy(); Cglib可以代理类,通过ASM实现的,JDK只能代理接口。原理都是jvm动态生成字节码。
2.I/O BIO: 同步阻塞I/O,内核态拷贝到用户态阻塞 NIO: 同步非阻塞I/O,通过selector监听多个通道,非阻塞,处理完成后返回。线程首先发送select调用,询问内核数据是否准备就绪,内核准备好再发read。 AIO: 通知回调。
3.集合
ArrayList: Object[] 数组 Vector: Object[] 数组 LinkedList: 双向链表。1.6及以前是循环链表。 HashSet: 无序、唯一 HashMap LinkedHashSet: LinkedHashMap实现的。 TreeSet: 有序、唯一 红黑树(自平衡的排序二叉树) HashMap: 数组+链表+红黑树(链表阈值8,扩容因子0.75),1.7及以下数组+链表 LinkedHashMap: HashMap的基础上,Node增加了一个双向链表。 Hashtable: 数组+链表 TreeMap: 红黑树(自平衡的排序二叉树)
3.1. list 构造函数:如果没有指定size,jdk7默认构造一个10的数组,jdk8默认构造空数组。 add: 得到最小扩容量(size+1, 10),如果size+1超过了当前容器的最大size,进行grow(),扩容至1.5倍。最大可以到Integer.MAX_VALUE
3.2. HashMap 3.2.1. 长度为什么是2的幂次方? 2的幂次方的&操作 = %取余操作。采用二进制位操作&能提高运算效率。 3.2.2. 死循环问题? jdk7两个线程同时扩容的时候,新链表和旧链表顺序是反的,会出现A.next=B,B.next=A,get()产生死循环。jdk8新链表和旧链表顺序是一致的,头插改成尾插,解决了这个问题。 3.2.3. EntrySet比KeySet遍历性能高? 因为keySet相当于又调用了一遍map.get(key); put:
3.3. ConcurrentHashMap jdk7用Segment分段锁,jdk8使用Node 数组+链表+红黑。 锁是非公平的。 树的数组结构来实现,并发控制使用sychronized和CAS操作。(jdk1.6对sychronized锁做了很多优化)。 初始化:通过自旋和CAS完成的,sizeCtl -1 - 说明正在初始化;-N表示有N-1个线程正在扩容; >0 如果没有初始化,则表示table初始化大小,如果初始化了表示table容量。 put: 1.根据key计算出hashcode;2.判断是否要初始化;3.即为当前key定位出的Node,如果为空表示当前位置可以写入数据,利用CAS尝试写入,失败则自旋保证成功。4.如果当前位置的hashcode == MOVED == -1,则需要扩容; 5.如果都不满足,sychronized锁写入;6.如果数量>TREEIFY_THERSHOLD,则要转换为红黑树。 get:1. 根据hash计算位置;2.查找指定位置,如果头节点就找到,返回value;3.如果头节点hash<0,说明正在扩容或者红黑树,查找;4.如果是链表,查找; size插入:1. counterCells == null,对baseCount做cas操作;2. cas失败使用countCells[];3. countCells插入失败再试下baseCount;整个过程自旋循环。 扩容:触发条件 1. 添加新元素达到阈值 2. 链>8且Map<64 3. putAll放不下元素 后边细看。第一个线程sizeCtl -2,后边线程发现在扩容,协助扩容。每个线程负责一段。扩容的时候fwd标识类,用于指向新表。
- 并发 4.1. 线程与进程 进程是系统运行程序的基本单位,线程是比进程更小的单位。与进程不同的是,多个线程共享进程的堆和方法区的资源,私有程序计数器,虚拟机栈,本地方法栈。同一进程中的线程可能相互影响。 4.2. 程序计数器、虚拟机栈和本地方法栈为什么是私有? 程序计数器主要用来记录当前线程执行的位置,为了线程切换恢复后能到正确的位置继续执行;虚拟机栈每个java方法会创建一个栈帧入栈,栈帧里包括了局部变量表,操作数栈,常量池引用等信息。从方法调用直至执行结束,其实就是对应栈帧从入栈到出栈的过程。为了保证局部变量不被其他线程访问到,所以是私有的。 4.3. 线程生命周期?
4.4. 什么是上下文切换? 当前任务在执行完CPU时间片切换到另一个任务前会保存自己的状态,以便再次切换到这个任务时,可以加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。 4.5. 死锁 A/B都持有对方资源,死锁。避免死锁,顺序获取锁,一次性请求锁,如果得不到释放锁。 4.6. sleep(),wait() wait释放锁,要notitfy唤起,sleep不释放锁。 4.7. run(),start() start()会把线程变成就绪状态,系统自动调run(),直接调run()就是普通调run() 4.8. synchronized static和sychronized(class)都是给Class类上锁,sychronized关键字加到实例方法上是给对象实例上锁。 jvm底层原理,static 执行sychronized字节码里可以看到monitorenter和monitorexit。monitorenter会争夺monitor对象的持有权,当计数=0时可获取,monitorexit计数-1,当计数=0释放。 method ACC_SYNCHRONIZED标记。sychronized时候会将对象头的markword cas写入。无锁-对象hashcode,偏向锁-线程id,轻量级锁-栈中锁记录指针,其他线程自旋,重量级锁-互斥锁指针,其他线程阻塞 4.9. volatile 因为JMM,主存改值而本地内存还在使用老值引起的可见性问题。volatile解决这个问题。可见性-内存屏障, 顺序性-禁止指令重排序。
4.10. Threadlocal Thread里有ThreadLocalMap,存储ThreadLocal为Key,Object为value的对象。其中key为弱引用。threadlocal hash采用线性探索的hash方法来进行存储,存储时会找前后key为null的值清除,防止内存泄漏问题。 InheritableThreadLocal 子类初始化时候先继承父类的InheritableThreadLocal 4.11. 线程池 核心参数 核心线程,最大线程,过期时间,过期时间单位,queue,threadFactory,拒绝策略。拒绝策略:直接丢弃,抛异常丢弃,用调用自己的线程执行任务,丢弃最早任务。
4.12. AtomicInteger volatile + 系统cas
4.13. AQS 思想:当共享资源空闲,将当前请求线程的资源设为有效线程,锁定资源。当共享线程锁定,需要一套线程阻塞等待以及唤醒时分配锁的机制,这个机制就是用AQS的CLH队列(虚拟双向队列)实现的,将获取不到锁的线程加入队列。
4.14. Semaphore 信号量-允许多个线程同时访问 semaphore.acquire(1); semaphore.release(1); state > 0 ,可以进入,state-1,不然挂在AQS等唤起。 4.15. countDownLatch 倒计时器- 没执行完阻塞 state == 0, 唤起主线程
4.16. cyclicBarrier 循环栏栅 有reset可以多次使用
4.17. 读写锁 state高16位:记录进入读锁的线程数+重入次数,统称读锁次数。 state低16位:记录写锁的重入次数,统称写锁次数。
4.18. ReentrantLock state =0 抢一下, tryAcquire && addNode && checkInterrupt