6---异常+进程和线程初步

本文深入讲解Java中的异常处理机制,包括异常的概念、体系结构及处理方式,并详细介绍多线程的基础知识,如进程与线程的区别、多线程的优点及创建线程的多种方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、异常

1、概念:

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

​ 异常本身是一个类,产生异常就是:创建异常这个对象并抛出这个异常对象。

2、Java中的异常体系:

​ (1)异常的根类是 java.lang.Throwable ,其下有两个子类:java.lang.Error 与 java.lang.Exception ,平常所说的异常指 java.lang.Exception 。

Throwable: 异常和错误的父类
    |---: Error错误,程序无法处理的
    |---: Exception异常,程序可以处理的
             |---: 编译时异常,必须处理的
             |---: 运行时异常,可以处理,可以不处理

(2)异常的分类:

​ a.编译时异常:RuntimeException及其子类都是运行时异常

​ 特点:代码一写完就报错,不处理通不过编译

​ a.运行时异常:除了运行时异常以外的都是编译时异常

​ 特点:不处理也能通过编译,运行才报错

3、JVM处理异常的方式———中断处理

​ 1.打印异常信息
​ 2.停止程序

4、异常的产生过程解析

(1)当程序执行到第20行,发现a[10]出现了s数组索引越界,就会创建一个ArrayIndexOutOfBoundsException异常对象

(2)异常出现后,先会在其所在的方法getNumber()中查看是否有处理异常的代码,没有的话进入下一步。

(3)将该异常抛给调用该方法的地方(main方法调用的,也就是抛给main方法),然后再在main()方法中查看该方法有没有处理异常的代码,没有,则进入下一步。

(4)将该异常交给JVM,JVM收到该异常后,做两件事:a.打印异常信息 b.停止程序

图解:

在这里插入图片描述

5、throw关键字【创建】异常

​ 1、throw的作用: 创建/产生/制造异常 (搞事情)

​ 2、使用格式:throw new 异常类名("异常的提示信息")

6、Java中处理异常的2种方式之【throws关键字处理异常】(不负责)

(1)throws概念:

​ 处理异常的一种方式,声明异常,,将异常抛给别人处理。(不负责任的)

(2)使用格式:

修饰符 返回值类型 方法名(参数列表) throws 异常的类名1, 异常的类名2 {
    ...
}

(3)throw 和 throws的区别:

​ throw:
​ 作用:** 创建/制造异常
格式: throw new 异常类名(); throw后面跟的是异常的对象
​ 数量: throw后面只能跟一个异常对象

​ throws:
​ 作用: 处理异常的一种方式,声明异常,让别人处理异常
​ 格式: throws后面跟的是异常的类名
​ 数量: throws后面可以跟多个异常的类名

7、Java中处理异常的2种方式之【try…catch处理异常 】(负责)

(1)概念:

​ 处理异常的一种方式,不同于throws,而是处理该异常。(负责任的)

(2)使用格式:

try {
    可能出现问题的代码;
} catch (异常类名 变量名) {
    处理异常的代码;
}

(3)处理异常时代码执行情况

代码没有问题的情况:
    1.先执行try中代码,发现try中代码没有异常
    2.执行完try中所有代码
    3.不会执行catch中的代码
    4.走catch后面代码

代码有问题的情况:
    1.先执行try中代码,发现try中代码有异常
    2.不会执行try后续的代码
    3.跳到catch中处理异常(执行cash中的代码)
    4.走catch后的代码

(4)try…catch处理异常的好处

​ 当出现异常后,我们处理了异常,程序会接着往下走,不会崩溃

(5)try…catch处理多异常

try {
    可能出现异常的代码;
} catch (异常类名1 变量名) {
    处理第一个异常的代码;
} catch (异常类名2 变量名) {
    处理第二个异常的代码;
}

注意:异常可以抓父类,但是父类异常放后面。(否则会报错(编译报错))

// 异常可以抓父类,但是父类异常放后面
//Exception是ArrayIndexOutOfBoundsException的父类
try {
    int c = a / b;
    System.out.println(c);

    int x = arr[10];
    System.out.println(x);
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("aaaaaaaaaaaa");
} catch (Exception e) {
    System.out.println("我处理了除数为0的异常");
}
8、finally代码块

(1)概念:

​ 代码块被finally修饰,则该代码块的代码一定会被执行

(2)使用格式:

finally {
    一定会被执行的代码
}

常与try…catch一起使用

try {
    可能出现异常的代码
} catch (异常类名 变量名) {
    处理异常的代码
} finally {
    一定会被执行的代码
}

(3)使用效果:一定执行

​ (即使该代码块在return语句后面,也一定会执行)

public static void main(String[] args) {
    int a = 10;
    int b = 0;

    try {
        int c = a / b;
        System.out.println(c);
    } catch (ArithmeticException e) {
        System.out.println("处理除数为0的异常");
        return; // 结束方法
    } finally {
        System.out.println("我是一定会被执行的代码");
        System.out.println("我是一定会被执行的代码");
    }

    System.out.println("走我了吗?");
}

(4)使用场景

​ 一定会被执行的代码就放到finally中。如IO流的关闭,释放数据库的连接,释放锁 (释放资源的代码)。

9、异常类Throwable中的常用方法

String getMessage() 返回此throwable的详细消息字符串。(返回异常的原因)
String toString() 返回此throwable的简短描述。(返回异常的类名和原因)
void printStackTrace() 将此throwable和其追溯打印到标准错误流。打印异常的堆栈信息

public static void main(String[] args) {
    int a = 10;
    int b = 0;
    System.out.println("我是黑色");
    // err表示错误信息,在控制台显示的文字是红色的
    System.err.println("我是红色");

    try {
        int c = a / b;
        System.out.println(c);
    } catch (ArithmeticException e) {
        System.out.println(e.getMessage()); // / by zero
        System.out.println(e.toString()); // java.lang.ArithmeticException: / by zero
        e.printStackTrace(); // java.lang.ArithmeticException: / by zero at com.itheima.demo08异常中常用方法.Demo08.main(Demo08.java:21)
    }
}
10、关于类在抛出(throws)异常时的注意事项:
1.子类重写方法throws的异常要比父类方法throws的异常少(见父、子类方法的test01()和Test02()方法。
2.子类重写方法throws的异常可以是父类方法throws异常的子类
3.父类方法没有throws异常,子类方法只能try...catch处理

(PS:原则:异常越少越好)

见以下代码:

父类

class Fu {
    public void test01() throws IOException, ParseException {}
    public void test02() throws IOException {}
    public void test03() {}

子类

class Zi extends Fu {
    // 1.子类重写方法throws的异常要比父类方法throws的异常少
    @Override
    public void test01() throws IOException {}

    // 2.子类重写方法throws的异常可以是父类方法throws异常的子类
    @Override
    public void test02() throws FileNotFoundException {}

    // 3.父类方法没有throws异常,子类方法只能try...catch处理
    @Override
    public void test03() {
        try {
            FileInputStream fis = new FileInputStream("abc");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}
10、自定义异常

(1)为什么要自定义异常

​ JDK提供的异常虽然比较多,但是不一定符合我们的需求。此时我们可以根据自己的业务来定义异常类,例如年龄负数问题,考试成绩负数问题。(这里的自定义异常都是指定义运行时异常。)

(2)自定义异常的步骤:

​ 1.定义类继承Exception/RuntimeException
​ 2.编写构造方法,调用父类的构造方法

见代码

// 1.定义类继承Exception
// 年龄小于18的异常
public class U18Exception extends Exception {
    // 2.编写构造方法,调用父类的构造方法
    public U18Exception() {
    }

    // 提供错误信息
    public U18Exception(String message) {
        super(message);
    }
}
// 网站注册,年龄小于18,有问题
public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    System.out.println("请输入姓名");
    String name = sc.next();
    System.out.println("请输入年龄");
    int age = sc.nextInt();

    //register(name,age);//报错。方法抛出的异常,要处理

    // 调用的地方,应该要处理这个异常
    try {
        register(name, age);
    } catch (U18Exception e) {
        System.out.println("处理了年龄小于18的异常, 您的年龄太小,请回去拿父母的身份证");
    }
}

// 注册的方法
public static void register(String name, int age) throws U18Exception {
    // 判断年龄
    if (age < 18) {
        // 非法年龄,不让注册,创建一个异常, 【自己搞出来的异常,跑出去,让别人来处理】
        throw new U18Exception("您的年龄不合法");
    } else {
        System.out.println("恭喜您,请尽情浏览...");
    }
}
11、当我们在工作中遇到异常时候的处理方式:
运行时异常: 使用if条件判断,过滤特殊情况
编译时异常:
    1.alt + 回车 -> Add exception to method signature (throws处理异常,不负责任的)
    2.alt + 回车 -> Surround with try/catch (try...catch处理异常,负责任的)

例如:

public static void main(String[] args) {
    // 运行时异常
    int a = 10;
    int b = 0;

    if (b != 0) {
        int c = a / b;
        System.out.println(c);
    } else {
        System.out.println("提示用户除数为0啦");
    }

    // 编译时异常
    try {
        FileInputStream fis = new FileInputStream("abv");
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
}

2、进程与线程

1、并发与并行

​ 并行: 两个或以上的事件在同一时刻发生(同时发生)

​ 并发: 两个或以上的事件在一个时间段内发生(交替执行)

2、线程与进程

​ (1)进程:正在运行的程序

​ 如我们电脑上会安装很多的应用程序比如Notepad++,微信等等,这些程序安装好后都是在电脑硬盘上面

​ (2)线程:线程是进程的执行单元(线程是用来执行程序的代码的)

​ a、Java使用 java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或其子类的实例。(通过Thread类来创建线程)

​ b、主线程:执行main方法的那个线程就是主线程

3、多线程同时执行

(1)原理:CPU同一时刻只能处理一个线程.CPU在多个线程间快速切换,造成多个程序同时执行的"假象"

(2)优点:

​ 1.让多件事情同时处理,提高效率。
​ 2.提高资源的利用率(百度云案例解释)

(3)注意:

​ 多线程一定能提高程序的运行速率吗

​ 不一定。(1)因为线程越多,CPU耗费在切换线程的时间会变长,降低速度;

​ (2)因为线程越多,每个线程得到执行的机会将会减少,降低速度。

4、创建线程

通过Thread类来创建线程,步骤

1.定义类继承Thread
2.重写run方法
3.创建子类对象
4.调用start方法启动线程

Ps:我们为什么要继承Thread类来创建进程,而不直接用Thread类来创建其对应的线程对象?

见代码解释

public static void main(String[] args) {
    System.out.println("来啦!");

    Thread t = new Thread();
    t.start();
    // t.start()会调用系统资源创建出一个新的线程,这个新的线程会执行t.run();
    // Thread类中的run方法不是我们想要执行的代码

    MyThread mt = new MyThread();
    // mt.run();//不调用start(),直接调用run,就不会有新的线程创建并运行
    mt.start();
    // mt.start();会调用系统资源创建出一个新的线程,【新的线程会执行mt.run();】
    // 找到MyThread类的run方法,这个run方法是我们重写的方法,就是我们要执行的代码
}

​ 总结:谁.start()新线程就调用谁的run()方法。而t.start()的run方法不是我们想要执行的代码。而mt.start()才是。

5、Thread类中的方法

​ (1)获取线程名称的方式:

​ String getName() 返回此线程的名称

​ (2)修改线程名称的方式:

​ 普通方法:
​ void setName(String name) 修改线程的名称
​ 构造方法:
​ Thread(String name)

获取名字:

public class NamedThread extends Thread {
    @Override
    public void run() {
        System.out.println("run: " + getName());//在线程内部直接调用getName方法
        for (int i = 0; i < 10; i++) {
            System.out.println("run: " + i);
        }
    }
}

public static void main(String[] args) {
    NamedThread nt = new NamedThread();
    System.out.println("main: " + nt.getName());//在main方法内部通过对象的getName方法获取线程的名字

    nt.start();
}

修改名字

public class NamedThread extends Thread {
    public NamedThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println("run: " + getName());
        for (int i = 0; i < 10; i++) {
            System.out.println("run: " + i);
        }
    }
}
public static void main(String[] args) {
    NamedThread nt = new NamedThread("下载");
    // 修改线程的名称
    // nt.setName("听音乐");
    System.out.println("main: " + nt.getName());

    nt.start();
}

​ (3)Thread类中的静态方法currentThread()

static Thread currentThread() 返回对当前正在执行的线程对象的引用。(获取当前线程)

public static void main(String[] args) {
    System.out.println("开始");
    Thread thread = Thread.currentThread();
    System.out.println(thread.getName());
    System.out.println(thread.toString()); // Thread[main,5,main]              									 			 //(thread类修改了toString()方法)
    // main:线程的名称
    // 5: 线程的优先级
    // main: 线程组的名称
}

​ (4)Thread类中的静态方法sleep(long millis)

static void sleep(long millis) 让执行这行代码的线程睡眠指定的时间

public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("run: " + i);

            try {
                Thread.sleep(1000);//出现编译异常。但是因为父类Thread没有抛异常,所以子类MyThread就不能抛异常。此时只能处理异常(try..catch语句)
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public static void main(String[] args) throws InterruptedException {
    System.out.println("开始");
    System.out.println(1);
    Thread.sleep(3000); // 3s//出现异常,抛出
    System.out.println(2);

    MyThread mt = new MyThread();
    mt.start();
}

​ (5)线程的礼让方法

static void yield() 对调度程序的一个暗示,当前线程不太想执行了,让CPU尽量切换到其他线程
6、创建线程的另一种方式:实现Runnable接口创建线程

1、Runaable接口

  • Runnable接口应由任何类实现,其实例将由线程执行。 该类必须定义一个无参数的方法,称为run

2、为什么要实现接口创建线程
(1)相比于继承Thread的方式创建线程,它只能单继承;而实现Runnable接口的方式创建线程,还能继承其他类,更加灵活
(2)实现Runnable接口方式创建线程,将任务(run方法的重写)和 线程的启动分开,降低了代码的耦合性

3、实现接口创建线程的步骤
1.定义类实现Runnable接口
2.重写run方法
3.创建实现类对象
4.创建Thread的对象,将Runnable实现类对象作为参数(thread的另一个含参构造方法)
	Thread(Runnable target) 分配一个新的 Thread对象。 
5.调用start启动线程

实现Runnable接口

// 1.定义类实现Runnable接口
public class MyRunnable implements Runnable {
    // 2.重写run方法
    @Override
    public void run() {
        // 写在新线程上要执行的代码(任务)
        for (int i = 0; i < 20; i++) {
            System.out.println("run: " + i);
        }
    }
}
public static void main(String[] args) {
    // 3.创建实现类对象
    MyRunnable mr = new MyRunnable();

    // 4.创建Thread的对象,将Runnable实现类对象作为参数
    Thread t = new Thread(mr);
    // 5.调用start启动线程
    t.start(); // t.start();调用系统资源创建出新的线程,新的线程执行参数的run方法

    for (int i = 0; i < 20; i++) {
        System.out.println("main: " + i);
    }
}
4、有了Runnable,则可以使用匿名内部类方式创建线程
public static void main(String[] args) {
    // 参数是接口: 1.传入接口的实现类对象 2.传入匿名内部类
    // Thread(Runnable target)
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("我是匿名内部类方式新建的线程");
        }
    });
    
    t1.start();

    // 简写
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("我是简化的方式新建的线程");
        }
    }).start();//直接调用了start方法
}

行参数的run方法

for (int i = 0; i < 20; i++) {
    System.out.println("main: " + i);
}

}


#### 4、有了Runnable,则可以使用匿名内部类方式创建线程

```java
public static void main(String[] args) {
    // 参数是接口: 1.传入接口的实现类对象 2.传入匿名内部类
    // Thread(Runnable target)
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("我是匿名内部类方式新建的线程");
        }
    });
    
    t1.start();

    // 简写
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("我是简化的方式新建的线程");
        }
    }).start();//直接调用了start方法
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值