Day28
1.异常机制
1.1 异常概念
异常是程序在运行期发生的不正常的事件,它会打断指令的正常执行流程。
设计良好的程序应该在异常发生时提供处理这些不正常事件的方法,使程序不会因为异常的发生而阻断或产生不可预见的结果。
Java语言使用异常处理机制为程序提供了异常处理的能力
在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象。Java处理异常的方式是中断处理。
1.2 异常分类
Java程序运行过程中所发生的异常事件从严重性可分为两类:
1、 错误(Error):JVM系统内部错误或资源耗尽等严重情况-属于JVM需要负担的责任这一类异常事件无法恢复或不可能捕获,将导致应用程序中断。
2、 异常(Exception):其它因编程错误或偶然的外在因素导致的一般性问题。这类异常得到恰当的处理时,程序有机会恢复至正常运行状况。
1.2.1 错误出现的情况
错误(Error):JVM系统内部错误或资源耗尽等严重情况,属于JVM需要负担的责任,这一类异常事件无法恢复或不可能捕获,将导致应用程序中断。
错误Error,无法通过处理的错误,只能事先避免,好比绝症。
public class Test01 {public static void main(String[] args) {//StackOverflowError - 栈内存溢出的错误//出现原因:调用方法会在栈中开辟空间,用于存放该方法的局部变量,死循环的调用方法,栈内存就满载并溢出了method();}public static void method(){method();}
}
public class Test02 {public static void main(String[] args) {//OutOfMemoryError -- 内存溢出的错误//出现原因:new出来的数组添加到集合中,该数组不会被回收ArrayList<byte[]> list = new ArrayList<>();while(true){byte[] bs = new byte[8192];list.add(bs);} }
}
1.2.2 异常出现的情况
异常(Exception):其它因编程错误或偶然的外在因素导致的一般性问题。
这类异常得到恰当的处理时,程序有机会恢复至正常运行状况。
分类:
RuntimeException – 非受检性异常
一般性异常 --------- 受检性异常
非受检性异常
RuntimeException(非受检性异常):
编译器不要求强制处置的异常。一般是指编程时的逻辑错误。是程序员应该积极避免其出现的异常。
注意:java.lang.RuntimeException及它的子类都是非受检异常
错误的类型转换:java.lang.ClassCastException
数组下标越界:java.lang.ArrayIndexOutOfBoundsException
空指针访问:java.lang.NullPointerException
算术异常(除0溢出):java.lang.ArithmeticException
public class Test03 {public static void main(String[] args) {//ArithmeticException -- 算数异常System.out.println(10/0);//ArrayIndexOutOfBoundsException -- 数组下标越界异常int[] arr = {1,2,3};System.out.println(arr[1000]);//IndexOutOfBoundsException - 下标越界异常ArrayList<Object> list = new ArrayList<>();System.out.println(list.get(100));//ClassCastException - 类型转换异常Object obj = new Integer(100);String str = (String) obj;System.out.println(str);//NullPointerException --空指针异常String str = null;method(str);}public static void method(String str) {System.out.println(str.length());}}
受检性异常
一般性异常(受检性异常):编译器要求必须处置的异常。指的是程序在运行时由于外界因素造成的一般性异常。
没有找到指定名称的类:java.lang.ClassNotFoundException
访问不存在的文件:java.io.FileNotFoundException
操作文件时发生的异常:java.io.IOException
操作数据库时发生的异常:java.sql.SQLException
public class Test08 {public static void main(String[] args) throws ClassNotFoundException { //ClassNotFoundException -- 类未找到异常Class<?> clazz = Class.forName("java.lang.String111111");System.out.println(clazz);//FileNotFoundException - 文件未找到异常FileInputStream fis = new FileInputStream("C:\\Users\\Desktop\\异常理解图.png");System.out.println(fis);}}
1.3 Java的异常处理机制(面试题)
1.Java程序在执行过程中如果出现异常,会自动生成一个异常类对象, 该异常对象将被自动提交给JVM,这个过程称为抛出(throw)异常。
2.当JVM接收到异常对象时,会寻找能处理这一异常的代码
3.1 找到了 - 把当前异常对象交给其处理,这一过程称为捕获(catch)异常和处理异常。
3.2 没找到 - 运行时系统将终止,相应的Java程序也将退出。
1.4 Java处理异常的能力
1.try…catch…
2.throws
3.throw
1.4.1 try…catch…
语法结构:
try{
… //可能产生异常的代码
}catch( ExceptionName1 e ){
… //异常的处理代码
}catch( ExceptionName2 e ){
… //异常的处理代码
} finally{
… //无论如何都会执行的语句
}
理解:
try:该代码块中编写可能产生异常的代码。
catch:用来进行某种异常的捕获,实现对捕获到的异常进行处理。
场景:处理一个异常的情况
public class Test01 { public static void main(String[] args) {Scanner scan = new Scanner(System.in);System.out.println("请输入一个数字:");int a = scan.nextInt();System.out.println("请输入第二个数字:");int b = scan.nextInt();try{System.out.println("111");System.out.println(a/b);System.out.println("222");}catch (ArithmeticException e) {System.out.println("处理算数异常");}finally {scan.close();}}
}
场景:处理多个异常,但处理方式不一样的情况
注意:
1.finally代码块根据需求可写可不写
2.经验告诉我们关闭资源的代码一般写在finally
3.try…catch…可以有多个catch
4.try 代码段包含的是可能产生异常的代码
5.先捕获的异常范围不能大于后捕获的异常范围
6.当异常发生时,程序会中止当前的流程去执行相应的catch代码段。
7.finally段的代码无论是否发生异常都执行
public class Test02 {public static void main(String[] args) {Scanner scan = new Scanner(System.in);System.out.println("请输入一个数字:");int a = scan.nextInt();System.out.println("请输入第二个数字:");int b = scan.nextInt();System.out.println("请输入类的全路径:");String classPath = scan.next();try{System.out.println("111");System.out.println(a/b);System.out.println("222");Class<?> clazz = Class.forName(classPath);System.out.println(clazz.getName());System.out.println("333");}catch (ArithmeticException e) {System.out.println("处理算数异常");}catch (ClassNotFoundException e){System.out.println("处理类未找到异常");}finally {scan.close();}}}
1.4.2 throws
在定义一个方法的时候可以使用throws关键字声明,使用throws声明的方法表示此方法不处理异常,而交给方法的调用出进行处理。
1.本身的程序处理不了了,往上一层抛,由上一层去处理
2.定义一个方法的时候,通过这种方式来告知调用方,我这个方法有可能会发生异常的。
语法结构:
public void method() throws 异常1,异常2{}
理解:
throws是抛出异常
抛给方法的调用方
一层一层往上面抛,最后try…catch…处理
注意:此时抛出的是异常类型,并且抛出的异常类型是紧跟在方法名之后。
关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常).
public class Test01 {public static void main(String[] args) {try {method01();} catch (ClassNotFoundException e) {System.out.println("处理异常");}}public static void method01() throws ClassNotFoundException{method02();}public static void method02() throws ClassNotFoundException{method03();}public static void method03() throws ClassNotFoundException{Scanner scan = new Scanner(System.in);System.out.println("请输入类的全路径:");String classPath = scan.next();Class<?> clazz = Class.forName(classPath);System.out.println(clazz.getName());scan.close();}
}
1.4.3 throw
异常不仅仅虚拟机可以抛,我们自己也可以抛。我们可以在代码中使用throw关键字(注意不带s)来抛出某个具体的异常对象。很多情况下我们会手动抛出运行时异常。
语法结构:
throw new 异常(); //抛给自己写的异常类
throw new NullPointerException(“要访问的arr数组不存在”); //直接解决,不用创建异常类
理解:
手动抛出异常—throw new RuntimeException(“程序出现了异常”);
throw 结合自定义异常使用(自己写一个异常类)
throw用在方法内,用来抛出一个异常对象,将这个异常对象传递到调用者处,并结束当前方法的执行。
public class Test01 {public static void main(String[] args) {Scanner scan = new Scanner(System.in);System.out.println("请输入一个数字:");int a = scan.nextInt();System.out.println("请输入第二个数字:");int b = scan.nextInt();try {if(b == 0){throw new MyException(); //throw new Exception("XXXX");}} catch (MyException e) { //这里也要写自己写的异常类 或者 Exception eb = 1;System.out.println(e);}System.out.println(a/b);scan.close();}
}
创建自定义异常,需要继承Exception 或其子类。
public class MyException extends Exception{@Overridepublic String toString() {return "除数不能为0的异常";}
}
多线程
1.什么是进程
进程是系统进行资源分配和调用的独立单元,每一个进程都有它的独立内存空间和系统资源。
2.单进程操作系统和多进程操作系统的区别
单进程操作系统:dos(一瞬间只能执行一个任务)
多进程单用户操作系统:Windows(一瞬间只能执行多个任务)
多进程多用户操作系统:Linux(一瞬间只能执行多个任务)
3.现在的多核CPU是否可以让系统在同一个时刻可以执行多个任务吗?
理论上是可以的
4.什么是线程,理解线程和进程的关系
什么是线程?
线程是进程里面的一条执行路径,每个线程同享进程里面的内存空间和系统资源
一个进程 可以有 多个线程:各个线程都有不同的分工
理解线程和进程的关系
进程 与 进程 之间的关系:进程之间的内存空间和系统资源是独立的
同一个进程里的多条线程 :线程之间的内存空间和系统资源是共享的
进程里:可以有一条或一条以上的线程
进程里只有一条线程的情况下,这条线程就叫做主线程
进程里有多条线程的情况下,只有一条线程叫做主线程
Ps:线程是在进程里的,他们是包含关系
5.我们应用的软件有哪些是多线程的应用?
都是
6.Java中,如何来编写多线程的应用程序?有哪些方法
6.1 创建线程 – 线程类(继承Thread类)
步骤:
1.创建线程类(MyThread),继承Thread,重写run方法
2.创建子线程 – MyThread t = new MyThread();
3.启动线程 ---- t.start();
public class Test01 {public static void main(String[] args) {//创建子线程MyThread t = new MyThread();//启动子线程t.start();}
}
public class MyThread extends Thread{//该线程的对象抢到CPU资源后,才会调用run方法@Overridepublic void run() {System.out.println("MyThread类中的run方法被调用了");}
}
void run() :在线程开启后,此方法将被调用执行
void start() :使此线程开始执行,Java虚拟机会调用run方法()
- 两个小问题
- 为什么要重写run()方法?
因为run()是用来封装被线程执行的代码
- run()方法和start()方法的区别?
run():封装线程执行的代码,直接调用,相当于普通方法的调用
start():启动线程;然后由JVM调用此线程的run()方法
6.2 创建线程 – 任务类(实现Runnable接口)
步骤:
1.创建任务类(Task),实现Runnable接口中的run方法
2.创建任务类对象 – Task task = new Task();
3.创建子线程,并把任务交给他 – Thread t = new Thread(task);
4.启动线程 – t.start();
public class Test01 {public static void main(String[] args) {//创建任务类对象Task task = new Task();//创建子线程,并把任务交给他Thread t = new Thread(task);//启动线程t.start();}
}
public class Task implements Runnable{//线程对象抢到CPU资源后才会调用run方法@Overridepublic void run() {System.out.println("Task类中的run方法被调用了");}}
3.带返回值的任务类
4.线程池
七、感受多线程之间争抢资源的场景
需求:编写一个多线程的应用程序,主线程打印1-100之间的数字,子线程打印200-300之间的数字,观察其输出的结果,体会多线程互相争抢资源的场景
public class Test01 {public static void main(String[] args) {MyThread t = new MyThread();t.start();for (int i = 1; i <= 100; i++) {System.out.println("主线程:" + i);}}
}
public class MyThread extends Thread{@Overridepublic void run() {for (int i = 200; i <= 300; i++) {System.out.println("子线程:" + i);}}
}
八、小结
进程 与 进程 的关系:独享内存空间和系统资源
线程 与 进程 的关系:有一个进程中至少包含一个线程
线程 与 线程 的关系:在同一个进程里,多个线程共享内存空间和系统资源
一个进程中包含多个线程,只有一个主线程
经典面试题:请问当我们编写一个单纯的main方法时,此时该程序是否为单线程的?为什么?
不是,是多线程,因为还有垃圾回收器
垃圾回收器是一个后台线程
九、线程的优先级别
含义:给线程定义抢到CPU资源的优先级
理解:
1.优先级别:1~10,数字越大,优先级越高
2.线程的优先级别不能决定线程是否优先抢到资源,优先级别只能影响(概率问题)
需求:在主线程中创3个子线程,并且设置不同优先级,观察其优先级对线程执行结果的”影响”。
a.setPriority(Thread.MAX_PRIORITY);//10
b.setPriority(Thread.NORM_PRIORITY);//5 — 默认的
c.setPriority(Thread.MIN_PRIORITY);//1
final int getPriority():返回此线程的优先级
final void setPriority(int newPriority):更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10
public class Test01 {public static void main(String[] args) {A a = new A();B b = new B();C c = new C();//设置优先级别 --- a抢到资源的概率大一点,c小一点a.setPriority(Thread.MAX_PRIORITY);//10b.setPriority(Thread.NORM_PRIORITY);//5c.setPriority(Thread.MIN_PRIORITY);//1a.start();b.start();c.start();}
}
public class A extends Thread{@Overridepublic void run() {for (int i = 1; i <= 100; i++) {System.out.println("A:" + i);}}
}
public class B extends Thread{@Overridepublic void run() {for (int i = 1; i <= 100; i++) {System.out.println("B:" + i);}}
}
public class C extends Thread{@Overridepublic void run() {for (int i = 1; i <= 100; i++) {System.out.println("C:" + i);}}
}
- 线程调度
- 两种调度方式
- 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
- 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
- Java使用的是抢占式调度模型
- 随机性
假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的
十、给线程自定义名称
- 自己写getThreadName/setThreadName
- 调用父类的一个参数的构造方法和getName
- Thread.currentThread().getName()
有多个类,有相同的功能,就可以不用一个一个创建类,直接创一个MyThread类,有参构造传参就可以了
需求:在主线程中创3个子线程,并且设置不同优先级,观察其优先级对线程执行结果的”影响”。
public class Test01 {public static void main(String[] args) {MyThread a = new MyThread("A");MyThread b = new MyThread("B");MyThread c = new MyThread("C");//设置优先级别a.setPriority(Thread.MAX_PRIORITY);//10b.setPriority(Thread.NORM_PRIORITY);//5c.setPriority(Thread.MIN_PRIORITY);//1a.start();b.start();c.start();}
}
public class MyThread extends Thread{private String threadName;public MyThread(String threadName) { //有参构造this.threadName = threadName;}@Overridepublic void run() {for (int i = 1; i <= 100; i++) {System.out.println(threadName + ":" + i);}}
}
另外一种写法:
public class MyThread extends Thread{public MyThread(String name) {super(name);}@Overridepublic void run() {//获取当前线程对象Thread t = Thread.currentThread();for (int i = 1; i <= 100; i++) {System.out.println(t.getName() + ":" + i);}}
}
String getName():返回此线程的名称
Thread currentThread() :返回对当前正在执行的线程对象的引用
十一、让线程休眠
需求:编写一个抽取学员回答问题的程序,要求倒数三秒后输出被抽中的学员姓名
Thread.sleep(1000);
此方法为静态方法,写在哪个线程中,哪个线程就休眠
public class Test01 {public static void main(String[] args) throws InterruptedException {String[] names = {"小浩","小李","小伟","小燕","小威","小彭","小王"};Random random = new Random();int index = random.nextInt(names.length);for(int i = 3;i>0;i--){System.out.println(i);//Thread的静态方法 -- sleep(毫秒),表示让当前线程休眠Thread.sleep(1000);}System.out.println(names[index]);// 3 2 1 小李}
}
static void sleep(long millis) :使当前正在执行的线程停留(暂停执行)指定的毫秒数
总结
1.异常
异常的概念
异常的分类
异常处理机制 – 面试题
异常处理的能力:
1.try…catch…
2.throws
3.throw
2.多线程
进程的概念
线程的概念
创建线程的方式(线程类、任务类)
线程的优先级别
给线程命名
线程的休眠