What:异常类型回答了什么被抛出
Where:异常堆栈跟踪回答了在哪抛出
Why:异常信息回答了为什么被抛出
从概念角度解析Java的异常处理机制:
1.Error:程序无法处理的系统处理,编辑器不做检查(如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢出等)
2.Exception:程序可以处理的异常,捕获后可能恢复
从责任角度看:
1.Error属于JVM需要负担的责任
2.RuntimeException是程序应该负担的责任
3.Checked Exception可检查异常是Java编译器应该负担的责任(编译时就应该捕获或者抛出的异常)
1.NullPointerException - 空指针引用异常
2.ClassCastException - 类型强制转换异常
3.IllegalArgumentException - 传递非法参数异常
4.IndexOutOfBoundsException - 下标越界异常
5.NumberFormatException - 数字格式异常
1.ClassNotFoundException - 找不到指定class的异常
2.IOException - IO操作异常
3.SQLException - SQL异常
1.NoClassDefFoundError - 找不到class定义的异常(原因:类依赖的class或者jar不存在;类文件存在,但是存在不同的域中;大小写问题,javac编译的时候是无视大小写的,很有可能编译出来的class文件就与想要的不一样)
2.StackOverflowError - 深递归导致栈被耗尽而抛出的异常
3.OutOfMemoryError - 内存溢出异常
private static int doSomething(){
try {
int i = 10/0;
System.out.println("i= " + i);
} catch (ArithmeticException e) {
System.out.println("ArithmeticException: " + e);
return 0;
} catch (Exception e) {
System.out.println("Exception: " + e);
return 1;
} finally {
System.out.println("Finally");
return 2;
}
}
public static void main(String[] args) {
System.out.println("执行后的值为:" + doSomething());
System.out.println("执行结束");
}
执行结果
ArithmeticException: java.lang.ArithmeticException: / by zero
Finally
执行后的值为:2
执行结束
尽量细化异常,别抛出异常的父类,也是为了方便定位问题
避免在finally中写return语句,其他return不会执行
在用户看来,应用系统发生的所有异常都是应用系统内部的异常
private static void testException(String[] array){
try {
System.out.println(array[0]);
}catch (NullPointerException e){
System.out.println("array cannot be null");
}
}
private static void testIf(String[] array){
if (array != null){
System.out.println(array[0]);
} else {
System.out.println("array cannot be null");
}
}
public static void main(String[] args) {
long start = System.nanoTime();
testException(null);
System.out.println("testException cost " + (System.nanoTime() - start));
}
// 运行结果
array cannot be null
testException cost 322997
public static void main(String[] args) {
long start = System.nanoTime();
testIf(null);
System.out.println("testIf cost " + (System.nanoTime() - start));
}
// 运行结果
array cannot be null
testIf cost 238152
Java8以前(不包含):数组+链表
数组长度默认是16,数组中每个元素存储着链表的头节点,通过hash(key.hashCode())%len(哈希函数取模操作)获得添加元素所要存放的数组的位置。
有个问题,如果哈希函数计算的值一直是同一个,会导致bucket(桶)过长,链表查询,就需要从头部开始遍历,最坏的情况下,性能会从O(1)变成O(n)。
Java8以后:数组+链表+红黑树
Java8之后做了调整,会通过一个常量TREEIFY_THRESHOLD控制是否将链接转成红黑树,性能从O(n)提高到O(logn)
HashMap使用懒加载,首次使用才初始化
HashMap:put方法的逻辑
1、如果HashMap未被初始化过,则初始化
2、对Key求Hash值,然后计算下标
3、如果没有碰撞,直接放入桶中
4、如果碰撞了,以链表的方式链接到后面
5、如果链表长度超过阈值,就把链表转成红黑树
6、如果链表长度低于6,就把红黑树转回链表
7、如果节点已经存在就替换旧值
8、如果桶满了(容量16*加载因子0.75),就需要resize(扩容2倍后重排)
HashMap:如何有效减少碰撞
HashMap:从获取hash到散列的过程
HashMap:扩容问题
注意点
如何优化Hashtable
通过锁细粒度化,将整锁拆解成多个锁进行优化,所以出现了ConcurrentHashMap
早期的ConcurrentHashMap:通过分段锁Segment来实现
数组+链表
当前的ConcurrentHashMap:CAS+synchronized使锁更细化
数组+链表+红黑树
ConcurrentHashMap:put方法的逻辑
1.判断Node[]数组是否初始化,没有则进行初始化操作
2.通过hash定位数组的索引坐标,是否有Node节点,如果没有则使用CAS进行添加(链表的头节点),添加失败则进入下次循环
3.检查到内部正在扩容,就帮助它一块扩容。
4.如果f!=null,则使用synchronized锁住f元素(链表/红黑二叉树的头元素)
4.1如果是Node(链表结构)则执行链表的添加操作
4.2如果是TreeNode(树型结构)则执行树添加操作
5.判断链表长度已经达到临界值8(默认值),当节点数超过这个值就需要把链表转换为树结构
ConcurrentHashMap总结
让主线程等待一组事件发生后继续执行,事件指的是CountDownLatch里的countDown()方法
阻塞当前线程,等待其他线程
控制某个资源可被同时访问的线程个数
主要用于线程间的数据交换,它提供一个同步点,一个线程到达同步点就会被阻塞,直到另外一个线程进入到同步点为止,两个线程到达同步点后,相互交换数据。
提供可阻塞的入队和出队操作,如果队列满了,入队操作将阻塞,直到有空间可用,如果队列空了,出队操作将阻塞,直到有元素可用。
主要用于生产者-消费者模式,在多线程场景时生产者线程在队列尾部添加元素,而消费者线程则在队列头部消费元素,通过这种方式能够达到将任务的生产和消费进行隔离的目的
主要有以下七个队列实现,都是线程安全的
1、ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(有界指的是容量大小有限制,先进先出)
2、LinkedBlockingQueue:一个由链表结构组成的有界/无界阻塞队列(先进先出)
3、PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列
4、DealyQueue:一个使用优先级队列实现的无界阻塞队列
5、SynchronousQueue:一个不存储元素的阻塞队列
6、LinkedTransferQueue:一个由链表结构组成的无界阻塞队列
7、LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列
Block-IO:其实是传统的java.net,java.io包下的接口或者类,比如java.net下的socket,servletSocket和http,因为网络通信都是IO行为,所以可以说是BIO的范畴,传统的IO基于字节流和字符流进行操作,比如InputStream和OutputStream,Reader和Writer。
BIO是基于流模型实现的,这意味着其交互方式是同步阻塞的,在读取输入流或者写入输出流时,在读写操作完成之前,线程会一直阻塞,它们之间的调用是可靠的线性顺序,程序发送请求给内核,然后由内核进行通讯,在内核准备好数据之前,线程是被挂起的,所以在两个阶段,程序都处于挂起状态,类比成ClientServlet模式,其实现模式为一个连接一个线程,即客户端有连接请求时,则服务端启动一个线程处理,待操作系统返回结果,如果这个连接不做任何事情,会造成不必要的线程开销,当然,我们可以通过线程池机制来改善。
特点:在IO执行的阶段,都被阻塞住了
好处:代码比较简单
缺点:IO扩展性和效率存在瓶颈
NonBlock-IO:构建多路复用的、同步非阻塞的IO操作,提供了更接近操作系统底层的高性能数据操作方式
特点:程序要不断去询问内核是否准备好
NIO核心
Asynchronous IO:基于事件和回调机制
AIO如何进一步加工处理结果
AIO连server都免了,所以是0:N
BIO适用于连接数少且固定的架构,这种方式对服务器的资源要求较高,1.4之前的唯一选择
NIO适用于连接数多且短的架构,比如聊天服务器,编程复制
AIO适用于连接数多且长的架构,比如相册服务器,可以充分调用OS来参与并发操作,编程复制,1.7才出现的