Java常用类库
Java异常
异常处理机制主要回答了三个问题
- 异常类型回答了什么被抛出
- 异常堆栈跟踪回答了在哪抛出
- 异常信息回答了为什么被抛出
Error和Exception的区别
- Error:程序无法处理的系统错误,编译器不检查
- Exception:程序可以处理的异常,捕获后可能恢复
- 前者是程序无法处理的错误,后者是可以处理的异常
Java的异常处理机制
- 抛出异常:创建异常对象,交由运行时系统处理
- 捕获异常:寻找合适的异常处理器处理异常,否则终止运行
Java异常处理原则
- 具体明确:抛出的异常应能通过异常类名和message准确说明异常的类型和产生异常的原因
- 提早抛出:应尽可能早的发现并抛出异常,便于精确定位问题
- 延迟捕获:异常的捕获和处理应尽可能延迟,让掌握更多信息的作用域来处理异常
Java异常处理消耗性能的地方
- try-catch块影响JVM的优化
- 异常对象实例需要保存栈快照等信息,开销较大
Java集合框架
HashMap
put方法的逻辑
- 如果HashMap未被初始化过,则初始化
- 对Key求Hash值,然后再计算下标
- 如果没有碰撞,直接放入桶中
- 如果碰撞了,以链表的方式连接到后面
- 如果链表长度超过阀值,就把链表转为红黑树
- 如果链表长度低于6,就把红黑树转回链表
- 如果节点已经存在就替换旧值
- 如果桶满了(容量16*加载因子0.75),就需要resize(扩容2倍后重排)
HashMap如何有效减少碰撞
- 扰动函数:促使元素位置分布均匀,较少碰撞几率
- 使用final对象,并采用合适的equals()和hashCode()方法
HashMap扩容问题
- 多线程环境下,调整大小会存在条件竞争,容易造成死锁
- rehashing是一个比较耗时的过程
ConccurentHashMap
如何优化HashMap
- 通过锁细粒度化,将整锁拆解成多个锁进行优化
put方法的逻辑
- 判断Node[]数组是否初始化,没有则进行初始化操作
- 通过hash定位数组的索引坐标,是否有Node节点,如果没有则使用CAS进行添加(链表的头节点),添加失败则进入下次循环
- 检查到内部正在扩容,就帮助它一起扩容
- 如果f!=null,则使用synchronized锁住f元素(链表/红黑二叉树的头元素)
- 如果是Node(链表结果)则执行链表的添加操作
- 如果是TreeNode(树形结构)则执行树添加操作
- 判断链表长度已经达到临界值8(默认值,也可以调整),当节点数超过这个值就需要把链表转换为树结构
比起Segment,锁拆得更细
- 首先使用无锁操作CAS插入头节点,失败则循环重试
- 若头节点已存在,则尝试获取头节点的同步锁,再进行操作
三者区别
- HashMap线程不安全,数组+链表+红黑树
- Hashtable线程安全,锁住整个对象,数组+链表
- ConcurrentHashMap线程安全,CAS+同步锁,数组+链表+红黑树
- HashMap的key、value均可为null,而其他的两个类不支持
JUC
并发工具类
- 闭锁CountDownLatch:让主线程等待一组事件发生后继续执行
- 事件指的是CountDownLatch里的countDown()方法
- 栅栏CyclicBarrier:阻塞当前线程,等待其他线程
- 等待其它线程,且会阻塞自己当前线程,所有线程必须同时到达栅栏位置后,才能继续执行
- 所有线程到达栅栏处,可以触发执行另一个预先设置的线程
- 信号量Semaphore:控制某个资源可被同时访问的线程个数
- 交换器Exchanger:两个线程到达同步点后,相互交换数据
BlockingQueue:提供可阻塞的入队和出队操作
主要用于生产者-消费者模式,在多线程场景时生产者线程在队列尾部添加元素,而消费者则在队列头部消费元素,通过这种方式能够达到将任务的生产和消费进行隔离的目的
- ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列
- LinkedBlockingQueue:一个由链表结构组成的有界/无界阻塞队列
- PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列
- DealyQueue:一个使用优先级队列实现的无界阻塞队列
- SynchronousQueue:一个不存储元素的阻塞队列
- LinkedTransferQueue:一个由链表结构组成的无界阻塞队列
- LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列
IO机制
- BIO(Block-IO:InputStream和OutputStream,Reader和Writer)
- NIO(NonBlock-IO:构建多路复用的、同步非阻塞的IO操作)
- Channels
- FileChannel:transferTo把FileChannel中的数据拷贝到另外一个Channel;transferFrom把另外一个Channel中的数据拷贝到FileChannel;避免了两次用户态和内核态间的上下文切换,即“零拷贝”,效率较高
- DatagramChannel
- SocketChannel
- ServerSocketChannel
- Buffers
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
- MappedByteBuffer
- Selectors
- Channels
- AIO(Asynchronous IO:基于事件和回调机制)
- 基于回调:实现CompletionHandler接口,调用时触发回调函数
- 返回Future:通过isDone()查看是否准备好,通过get()等待返回数据
- IO多路复用:调用系统级别的select\poll\epoll
- 支持一个进程所能打开的最大连接数
- select:单个进程所能打开的最大连接数由FD_SIZE宏定义,其大小是32个整数的大小(在32位的机器上,大小是3232,64位的机器上为3264),可以对其修改,重新编译内核,但是性能无法保证
- poll:本质上与select没有区别,但是它没有最大连接数的限制,因为它是基于链表来存储的
- epoll:虽然连接数有上限,但是很大,1G内存的机器上可以打开10万左右的连接
- FD剧增后带来的IO效率问题
- select:因为每次调用时都会对连接进行遍历,所以随着FD的增加会造成遍历速度的“线性下降”的性能问题
- poll:同上
- epoll:由于epoll时根据每个fd上的callback函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll不会有“线性下降”的性能问题,但是所有socket都很活跃的情况下,可能会有性能问题
- 消息传递方式
- select:内核需要将消息传递到用户空间,需要内核的拷贝动作
- poll:同上
- epoll:通过内核和用户空间共享一块内存来实现,性能较高
- 支持一个进程所能打开的最大连接数