Java语言进阶 #异常&多线程 #Day16 #异常 #创建/使用多线程

一、异常

异常: 程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。

  • Java等面向对象的语言而言,异常本身是一个类,产生异常就是创建一个异常对象并抛出这个异常对象。Java处理异常的方式是中断处理。

1. 异常体系

Throwable
Error
Exception
RuntimeException
  • Throwable: 意思是可抛出的
  • Error: 错误。不能避免的
  • Exception: 异常。使用不当导致的,可以避免
  • RuntimeException: 运行期异常

  • Exception子类:
    在这里插入图片描述

2. 异常的分类

异常: 程序得了小病,处理完即可

  • Exception: 编译期异常(不处理,编译就不能通过)
  • RuntimeException: 运行期异常(一般是由程序逻辑引起的错误)

错误: 程序得了大病,必须修改源代码


编译异常的处理:

  1. 交给虚拟机处理,直接抛出异常
    在这里插入图片描述
  2. 选中代码,按Alt 回车,选择try/catch。(后续代码会继续执行)
    在这里插入图片描述
    在这里插入图片描述

运行期异常:
在这里插入图片描述


错误:
在这里插入图片描述

3. 异常的产生过程

JVM检测出异常后,会做两件事:

  1. JVM会根据异常产生的原因,创建一个异常对象,这个异常对象包括(内容,原因,位置)
  2. 如果没找到异常处理逻辑(try…catch),就会把异常传递给调用者。(如果调用者也没有异常处理逻辑 ,就一路向上找,直到找到异常处理逻辑 。如果实在没有,就交给JVM处理)(其实很好理解,第二个调用调用者一般是main,第一个调用者一定是JVM

JVM接收到了异常对象,会做两件事:(JVM默认处理)

  1. 以红色字体,把这个异常打印在控制台
  2. 终止当前程序

4. 异常的处理

Java异常处理的五关键字:try、catch、finally、throw、throws

4.1 throw

throw new xxxException("异常产生的原因");

注意:

  1. throw关键字必须放在方法内部
  2. new的对象,必须是Eception或其子类对象
  3. 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

  1. throws关键字必须写在方法声明处
  2. throws后边声明的异常,必须是Exception或其子类
  3. 如果方法内部抛出多个异常,那么throws必须声明多个异常(如果抛出的异常,有子父类的关系,抛出父类异常即可)
  4. 如果调用了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

无论是否出现异常都会执行

  1. finally必须和try一起使用
  2. finally一般用于资源释放
try {
  // 这里如果抛出了异常,就会交给catch处理
} catch(类型1 参数1) {  // 可以有多个catch
  // 处理1
} finally {
  // 资源回收
}

在这里插入图片描述

注意: finally里如果有return语句,返回给调用处的,一定是finally里的return结果(比如tryfinally中,都有return,会返回finally中的)

4.7 多异常捕获

  1. 多个异常分别处理(用多个try...catch
  2. 多个异常一次捕获,多次处理(用一个try,多个catch
    catch里定义的异常变量,如果有子父类关系,那么catch里面定义的异常变量,必须写在上面,否则会报错(catch里的参数指向了这个错误对象)
  3. 多个异常一次捕获,一次处理(用一个trycatch能接收的是里面所有报错的父类异常)

4.8 子父类异常

父类异常怎么样,子类异常就怎么样。

  1. 父类抛出多个异常,子类也抛出相同的异常。
      或抛出父类异常的子类
      或不抛出异常
  2. 父类没有抛出异常,子类就不可以抛出异常(必须解决掉,不能声明抛出)

在这里插入图片描述

4.9 自定义异常类

  1. 继承自ExceptionRuntimeException
  2. 一个空参数的构造方法
  3. 一个带提示信息的构造方法

可以使用Alt Enter,直接使用父类重载形式

注意: 自定义异常类,类名要以Exception结尾(软性规定,表示其是一个异常类)

Demo:模拟注册操作

  1. 使用数组保存注册过的用户名(以后存到数据库)
  2. 使用Scanner获取用户输入的注册的用户名(以后用表单)
  3. 定义一个方法,对注册的用户名进行判断,如果用户名已经注册过,抛出注册异常

在这里插入图片描述

二、多线程

  • 并发:(交替执行) 多件事情,在同一时间段进行
  • 并行:(同时执行) 多件事情,在同一时间点进行

一个核心,只能同时处理一件事,那如何进行多任务?方法一是增加核心数,方法二是在进程间快速切换,快到我们无法察觉。后者就叫并发

1. 进程与线程

  • 进程: 即进行中的程序,是把硬盘中的资源和命令复制到内存中,然后通过命令操作对应的资源。

  • 线程: 一个执行单元,负责进程的执行。

  • 这是HelloWorld.s文件(汇编语言,看不懂没关系)。这样的一份文件,也叫程序,存在硬盘上(准确来说应该是.exe叫程序)
    在这里插入图片描述

  • 当点击HelloWorld.exe时,程序被复制到内存中开始运行,这就叫进程
    在这里插入图片描述

  • 代码一行行执行,我们想象有这样一个箭头,从上向下走。一个箭头就是一个线程(线程是应用程序与CPU间的执行路径,CPU可以在多个路径间做快速切换。)
    在这里插入图片描述

2. 线程的调度

线程的调度方式有两种:

  1. 分时调度: 轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
  2. 抢占式调度: 根据优先级进行调度,优先级高的优先使用CPU。(如果优先级相同,会随机选择一个,这称为线程随机性 ),Java的使用为抢占式调度

3. 主线程

  1. JVM执行main()方法,main()方法会执行到栈内存
  2. JVM会找操作系统,开辟一条通往CPU的执行路径
  3. CPU就可以通过这个路径来执行main()方法

这条路径就叫做主线程


  • 原始程序:
    在这里插入图片描述
  • 改造后程序: 我们让主线程抛异常,看看发生啥。会发现,后面的代码没执行。
    在这里插入图片描述

4. 创建多线程的第一种方式

创建多线程的第一种方式:创建Thread的子类

  1. 创建一个Thread的子类
  2. 在这个子类中重写run方法,设置线程任务(开启线程要做什么)
  3. 创建这个子类对象
  4. 调用start方法,开启新线程,来执行run方法
    在这里插入图片描述
    两个线程指的是:调用它的线程(这里指main线程)和新创建的线程。start只能调用一次,多次调用是会报错的。

  • 类文件中:
    在这里插入图片描述
  • 执行文件中:
    在这里插入图片描述

多次调用start时,会抛出** 非法线程start异常**
在这里插入图片描述

4.2 获取线程名称

  1. 方法1: 使用 Thread类中的方法getName()
    在这里插入图片描述
  2. 方法2: 使用静态方法Thread.currentThread()获取当前线程名
    在这里插入图片描述
    Thread.currentThread()使用getName()
    在这里插入图片描述

4.3 改变线程名称

  1. 方法1: 使用setName()方法
    在这里插入图片描述
  2. 方法2: 给父类传递一个带参数name的构造方法
    在这里插入图片描述

4.4 sleep方法

sleep方法可以让程序暂停,参数是(long 毫秒值)。注意,它是静态方法。(本身带异常,所以使用try..catch,当然,你也可以抛出)
在这里插入图片描述

5. 多线程内存图

多线程的原理是开辟多个栈空间

5.1 单线程程序

先写一个单线程程序: T类继承自Thread
在这里插入图片描述

  1. main()方法进栈,逐行执行,执行到第一行,在堆中创建一个对象
    在这里插入图片描述
  2. 调用run()方法,载入栈中
    在这里插入图片描述

这就是单线程程序

5.2 多线程程序

  1. main()方法进栈,逐行执行,执行到第一行,在堆中创建一个对象
    在这里插入图片描述
  2. 当执行到start()方法时,会开辟一个新的栈空间,同时这个新的栈空间里,放着run()方法
    在这里插入图片描述

多个线程互不影响,

6. 创建多线程的第二种方式

声明实现Runnable接口的类,然后实现run()。然后分配该类的实例,在创建Thread时,作为一个参数来传递并启动。

  1. 创建一个Runnable的实现类,要重写run方法
  2. 将这个实现类对象当参数传递
  3. 调用start()方法

  • 代码:
    在这里插入图片描述
  • 结果:
    在这里插入图片描述

6.1 两种方式的对比

  • 直接创建Thread类子类: 步骤少,一次性程序使用
  • 利用Runnable接口实现:
  1. 避免了单继承 的局限性
  2. 增强了程序的扩展性,进行了解耦

6.2 使用匿名内部类的方式

在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章