文章目录
一、异常
异常: 程序在执行过程中,出现的非正常的情况,最终会导致JVM
的非正常停止。
- 对
Java
等面向对象的语言而言,异常本身是一个类,产生异常就是创建一个异常对象并抛出这个异常对象。Java
处理异常的方式是中断处理。
1. 异常体系
- Throwable: 意思是可抛出的
- Error: 错误。不能避免的
- Exception: 异常。使用不当导致的,可以避免
- RuntimeException: 运行期异常
- Exception子类:
2. 异常的分类
异常: 程序得了小病,处理完即可
- Exception: 编译期异常(不处理,编译就不能通过)
- RuntimeException: 运行期异常(一般是由程序逻辑引起的错误)
错误: 程序得了大病,必须修改源代码
编译异常的处理:
- 交给虚拟机处理,直接抛出异常
- 选中代码,按
Alt 回车
,选择try/catch
。(后续代码会继续执行)
运行期异常:
错误:
3. 异常的产生过程
JVM检测出异常后,会做两件事:
JVM
会根据异常产生的原因,创建一个异常对象,这个异常对象包括(内容,原因,位置)- 如果没找到异常处理逻辑(try…catch),就会把异常传递给调用者。(如果调用者也没有异常处理逻辑 ,就一路向上找,直到找到异常处理逻辑 。如果实在没有,就交给
JVM
处理)(其实很好理解,第二个调用调用者一般是main
,第一个调用者一定是JVM
)
JVM接收到了异常对象,会做两件事:(JVM默认处理)
- 以红色字体,把这个异常打印在控制台
- 终止当前程序
4. 异常的处理
Java
异常处理的五关键字:try、catch、finally、throw、throws
4.1 throw
throw new xxxException("异常产生的原因");
注意:
- throw关键字必须放在方法内部
- new的对象,必须是Eception或其子类对象
- throw关键字抛出的指定的异常对象,我们就必须处理这个异常对象
3.1 如果创建的是运行期异常
, 可以不处理,交给JVM
默认处理
3.2 如果创建的是编译期异常
,要么throws
(交给别人处理), 要么try...catch
(自己处理)
参数的合法性效验: (补充知识,日后在工作中),如果参数不合法,我们就必须用抛出异常的方式,告诉方法的调用者(传递的参数有问题)
- 空指针异常属于运行期异常,默认给
JVM
处理即可。(索引越界异常也是运行期异常)
4.2 Objects非空的判断
- 先自己写一个判断:
- Objects中有专门处理空指针异常的:
4.3 throws
throws
是异常处理的第一种方式:交给别人处理(把异常交给调用者,最后交给JVM
)
修饰符 返回值类型 方法名(参数列表) throws 异常1,异常2... {
// 方法体;
}
注: 有没有觉得很熟悉? 在
2.异常的分类中
,那个main
方法后面被自动地加上了throws
throws
关键字必须写在方法声明处throws
后边声明的异常,必须是Exception
或其子类- 如果方法内部抛出多个异常,那么
throws
必须声明多个异常(如果抛出的异常,有子父类的关系,抛出父类异常即可) - 如果调用了
throws
声明抛出,必须自己处理(try…catch),或交给JVM
处理(继续throws
)
4.4 捕获异常
一般在工作中,会把异常记录到工作日志
try {
// 这里如果抛出了异常,就会交给catch处理
} catch(类型1 参数1) { // 可以有多个catch
// 处理1
} catch(类型2 参数2) {
// 处理2
}
4.5 三个异常处理办法
- getMessage(): 简短描述
- toString(): 详细描述
- printStackTrace(): 直接打印详细信息的方法
4.6 finally
无论是否出现异常都会执行
finally
必须和try
一起使用finally
一般用于资源释放
try {
// 这里如果抛出了异常,就会交给catch处理
} catch(类型1 参数1) { // 可以有多个catch
// 处理1
} finally {
// 资源回收
}
注意: finally
里如果有return语句,返回给调用处的,一定是finally
里的return
结果(比如try
和finally
中,都有return
,会返回finally
中的)
4.7 多异常捕获
- 多个异常分别处理(用多个
try...catch
) - 多个异常一次捕获,多次处理(用一个
try
,多个catch
)
catch
里定义的异常变量,如果有子父类关系,那么catch
里面定义的异常变量,必须写在上面,否则会报错(catch
里的参数指向了这个错误对象) - 多个异常一次捕获,一次处理(用一个
try
,catch
能接收的是里面所有报错的父类异常)
4.8 子父类异常
父类异常怎么样,子类异常就怎么样。
- 父类抛出多个异常,子类也抛出相同的异常。
或抛出父类异常的子类
或不抛出异常 - 父类没有抛出异常,子类就不可以抛出异常(必须解决掉,不能声明抛出)
4.9 自定义异常类
- 继承自
Exception
或RuntimeException
- 一个空参数的构造方法
- 一个带提示信息的构造方法
可以使用
Alt Enter
,直接使用父类重载形式
注意: 自定义异常类,类名要以Exception结尾(软性规定,表示其是一个异常类)
Demo:模拟注册操作
- 使用数组保存注册过的用户名(以后存到数据库)
- 使用
Scanner
获取用户输入的注册的用户名(以后用表单) - 定义一个方法,对注册的用户名进行判断,如果用户名已经注册过,抛出注册异常
二、多线程
- 并发:(交替执行) 多件事情,在同一时间段进行
- 并行:(同时执行) 多件事情,在同一时间点进行
一个核心,只能同时处理一件事,那如何进行多任务?方法一是增加核心数,方法二是在进程间快速切换,快到我们无法察觉。后者就叫并发
1. 进程与线程
-
进程: 即进行中的程序,是把硬盘中的资源和命令复制到内存中,然后通过命令操作对应的资源。
-
线程: 一个执行单元,负责进程的执行。
-
这是
HelloWorld.s
文件(汇编语言,看不懂没关系)。这样的一份文件,也叫程序,存在硬盘上(准确来说应该是.exe
叫程序)
-
当点击
HelloWorld.exe
时,程序被复制到内存中开始运行,这就叫进程
-
代码一行行执行,我们想象有这样一个箭头,从上向下走。一个箭头就是一个线程(线程是应用程序与CPU间的执行路径,CPU可以在多个路径间做快速切换。)
2. 线程的调度
线程的调度方式有两种:
- 分时调度: 轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
- 抢占式调度: 根据优先级进行调度,优先级高的优先使用CPU。(如果优先级相同,会随机选择一个,这称为线程随机性 ),
Java
的使用为抢占式调度 。
3. 主线程
JVM
执行main()
方法,main()
方法会执行到栈内存JVM
会找操作系统,开辟一条通往CPU
的执行路径CPU
就可以通过这个路径来执行main()
方法
这条路径就叫做主线程
- 原始程序:
- 改造后程序: 我们让主线程抛异常,看看发生啥。会发现,后面的代码没执行。
4. 创建多线程的第一种方式
创建多线程的第一种方式:创建Thread的子类
- 创建一个
Thread
的子类 - 在这个子类中重写
run
方法,设置线程任务(开启线程要做什么) - 创建这个子类对象
- 调用
start
方法,开启新线程,来执行run
方法
两个线程指的是:调用它的线程(这里指main线程
)和新创建的线程。start
只能调用一次,多次调用是会报错的。
- 类文件中:
- 执行文件中:
多次调用
start
时,会抛出** 非法线程start异常**
4.2 获取线程名称
- 方法1: 使用
Thread
类中的方法getName()
- 方法2: 使用静态方法
Thread.currentThread()
获取当前线程名
Thread.currentThread()
使用getName()
4.3 改变线程名称
- 方法1: 使用
setName()
方法
- 方法2: 给父类传递一个带参数
name
的构造方法
4.4 sleep方法
sleep
方法可以让程序暂停,参数是(long 毫秒值)。注意,它是静态方法。(本身带异常,所以使用try..catch
,当然,你也可以抛出)
5. 多线程内存图
多线程的原理是开辟多个栈空间
5.1 单线程程序
先写一个单线程程序: T
类继承自Thread
类
main()
方法进栈,逐行执行,执行到第一行,在堆中创建一个对象
- 调用
run()
方法,载入栈中
这就是单线程程序
5.2 多线程程序
main()
方法进栈,逐行执行,执行到第一行,在堆中创建一个对象
- 当执行到
start()
方法时,会开辟一个新的栈空间,同时这个新的栈空间里,放着run()
方法
多个线程互不影响,
6. 创建多线程的第二种方式
声明实现Runnable
接口的类,然后实现run()
。然后分配该类的实例,在创建Thread
时,作为一个参数来传递并启动。
- 创建一个
Runnable
的实现类,要重写run
方法 - 将这个实现类对象当参数传递
- 调用
start()
方法
- 代码:
- 结果:
6.1 两种方式的对比
- 直接创建Thread类子类: 步骤少,一次性程序使用
- 利用Runnable接口实现:
- 避免了单继承 的局限性
- 增强了程序的扩展性,进行了解耦