码迷,mamicode.com
首页 > 编程语言 > 详细

异常与多线程(一)

时间:2020-06-11 23:21:15      阅读:97      评论:0      收藏:0      [点我收藏+]

标签:catch   开启   结构图   指针   except   如何获取   管程   优先级   nal   

一、异常

1. 概述

异常:指的是程序在执行过程中,出现的非正常的情况,最终导致JVM虚拟机的非正常停止

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

异常指的并不是语法错误,语法错了,编译不通过,不会产生字节码文件,根本不能运行

2. 异常体系

异常机制其实是帮助我们找到程序中的问题,Java把异常当作对象来处理,并定义一个基类 java.lang.Throwable 作为所有异常的超类。

Throwable中的常用方法:
public void printStackTrace():打印异常的详细信息。
		包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace

public String getMessage()`:获取发生异常的原因
		提示给用户的时候,就提示错误原因

出现异常,不要紧张,把异常的简单类名,拷贝到API中去查。

在Java API中已经定义了许多异常类,这些异常类分为两大类,错误Error和异常Exception

  • Exception:用户程序可能捕捉的异常情况或者说是程序可以处理的异常
    • 编译期异常,进行编译(写代码)java程序出现的问题
    • RuntimeException:运行期异常,java程序运行过程中出现的问题
    • 异常就相当于程序得了一个小毛病。异常处理好,程序可以继续执行
  • Error:不希望被程序捕获或者是程序无法处理的错误
    • 错误就相当于程序得了大病,必须修改源代码,程序才能继续执行

Java异常层次结构图:

技术图片

从图中可以看出所有异常类型都是内置类Throwable的子类,因而 Throwable 在异常类的层次结构的顶层。

异常类 Exception 又分为运行时异常( RuntimeException )和非运行时异常。

3. 异常之间的区别与联系

Error

Error 类对象由 Java 虚拟机生成并抛出,大多数错误与代码编写者所执行的操作无关。
比如说:

  • Java虚拟机运行错误,当JVM不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError 。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止
  • 还有发生在虚拟机试图执行应用时,如类定义错误( NoClassDefFoundError )、链接错误( LinkageError )。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。

对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在Java中,错误通常是使用 Error 的子类描述

Exception

在 Exception 分支中有一个重要的子类 RuntimeException (运行时异常),该类型的异常自动为编写的程序定义如:

ArrayIndexOutOfBoundsException (数组下标越界)、NullPointerException (空指针异常)ArithmeticException (算术异常)、 MissingResourceException (丢失资源)、ClassNotFoundException (找不到类)

等异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。此异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生;

RuntimeException 之外的异常我们统称为非运行时异常,类型上属于Exception 类及其子类,从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException 、 SQLException 等以及用户自定义的 Exception 异常,一般情况下不自定义检查异常。
Error 和 Exception 的区别

  • Error 通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程
  • Exception 通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常

4. Java异常处理机制

java异常处理本质:抛出异常和捕获异常

抛出异常

要理解抛出异常

  • 首先要明白什么是异常情形,它是指阻止当前方法或作用域继续执行的问题
  • 其次把异常情形和普通问题相区分
    • 普通问题是指在当前环境下能得到足够的信息,总能处理这个错误。
    • 对于异常情形,已经无法继续下去了,因为在当前环境下无法获得必要的信息来解决问题,你所能做的就是从当前环境中跳出,并把问题提交给上一级环境,这就是抛出异常时所发生的事情
  • 抛出异常后,会有几件事随之发生。
    • 首先,是像创建普通的java对象一样将使用 new 在堆上创建一个异常对象
    • 然后,当前的执行路径(已经无法继续下去了)被终止,并且从当前环境中弹出对异常对象的引用
    • 此时异常处理机制接管程序,并开始寻找一个恰当的地方继续执行程序,这个恰当的地方就是异常处理程序或者异常处理器,它的任务是将程序从错误状态中恢复,以使程序要么换一种方式运行,要么继续运行下去

捕获异常

在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适的异常处理器,则运行时系统终止。同时,意味着Java程序的终止

注意:

  • 对于 运行时异常 、 错误 和 检查异常 ,Java技术所要求的异常处理方式有所不同
  • 由于运行时异常及其子类的不可查性,为了更合理、更容易地实现应用程序,Java规定,运行时异常将由Java运行时系统自动抛出,允许应用程序忽略运行时异常。
  • 对于方法运行中可能出现的 Error ,当运行方法不欲捕捉时,Java允许该方法不做任何抛出声明。因为,大多数 Error 异常属于永远不能被允许发生的状况,也属于合理的应用程序不该捕捉的异常

对于所有的检查异常,Java规定:一个方法必须捕捉,或者声明抛出方法之外。也就是说,当一个方法选择不捕捉检查异常时,它必须声明将抛出异常

二、异常的处理

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

1. throw关键字

作用:

  • 在java中提供了一个throw关键字,它用来抛出一个指定的异常对象。

格式:

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

例如:

throw new NullPointerException("要访问的arr数组不存在");

throw new ArrayIndexOutOfBoundsException("该索引在数组中不存在,已超出范围");

注意:

  • throw关键字必须写在方法的内部
  • throw关键字后边new的对象必须是Exception或者Exception的子类对象
  • throw关键字抛出指定的异常对象,我们就必须处理这个异常对象
    • throw关键字后边创建的是RuntimeException或者是 RuntimeException的子类对象,我们可以不处理,默认交给JVM处理(打印异常对象,中断程序)
    • throw关键字后边创建的是编译异常(写代码的时候报错),我们就必须处理这个异常,要么throws,要么try...catch
  • 以后我们首先必须对方法传递过来的参数进行合法性校验
    • 若参数不合法,就必须使用抛出异常的方式告知方法的调用者,床底的参数有问题

下面是一个例子:

public class Demo03Throw {
    public static void main(String[] args) {
        //int[] arr = null;
        int[] arr = new int[3];
        int e = getElement(arr,3);
        System.out.println(e);
    }
    /*
        定义一个方法,获取数组指定索引处的元素
        参数:
            int[] arr
            int index
        注意:
            NullPointerException是一个运行期异常,我们不用处理,默认交给JVM处理
            ArrayIndexOutOfBoundsException是一个运行期异常,我们不用处理,默认交给JVM处理
     */
    public static int getElement(int[] arr,int index){
        /*
            对传递过来的参数数组,进行合法性校验
            如果数组arr的值是null
            那么我们就抛出空指针异常,告知方法的调用者"传递的数组的值是null"
         */
        if(arr == null){
            throw new NullPointerException("传递的数组的值是null");
        }

        /*
            对传递过来的参数index进行合法性校验
            如果index的范围不在数组的索引范围内
            那么我们就抛出数组索引越界异常,告知方法的调用者"传递的索引超出了数组的使用范围"
         */
        if(index<0 || index>arr.length-1){
            throw new ArrayIndexOutOfBoundsException("传递的索引超出了数组的使用范围");
        }

        int ele = arr[index];
        return ele;
    }
}

注意:如果产生了问题,我们就会throw将问题描述类即异常进行抛出,也就是将问题返回给该方法的调用者。

那么对于调用者来说,该怎么处理呢?一种是进行捕获处理,另一种就是继续将问题声明出去,使用throws声明处理。

2. Objects非空判断

我们学习过一个类Objects,它由一些静态的实用方法组成,这些方法是null-save(空指针安全的)或null-tolerant(容忍空指针的)。在它的源码中,对对象为null的值进行了抛出异常操作。

  • public static <T> T requireNonNull(T obj):查看指定引用对象不是null。

查看源码发现这里对为null的进行了抛出异常操作:

public static <T> T requireNonNull(T obj) {
    if (obj == null)
      	throw new NullPointerException();
    return obj;
}

2.3 声明异常throws

throws关键字:异常处理的第一种方式,交给别人处理

  • 关键字throws运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常).

作用:

  • 当方法内部抛出异常对象的时候,那么我们就必须处理这个异常对象
  • 可以使用throws关键字处理异常对象,会把异常对象声明抛出给方法的调用者处理(自己不处理,给别人处理),最终交给JVM处理-->中断处理

使用格式:在方法声明时使用

修饰符 返回值类型 方法名(参数列表) throws AAAExcepiton,BBBExcepiton...{
            throw new AAAExcepiton("产生原因");
            throw new BBBExcepiton("产生原因");
            ...
        }

注意:

  • throws关键字必须写在方法声明处
  • throws关键字后边声明的异常必须是Exception或者是Exception的子类
  • 方法内部如果抛出了多个异常对象,那么throws后边必须也声明多个异常
    • 如果抛出的多个异常对象有子父类关系,那么直接声明父类异常即可
  • 调用了一个声明抛出异常的方法,我们就必须的处理声明的异常
    • 要么继续使用throws声明抛出,交给方法的调用者处理,最终交给JVM
    • 要么try...catch自己处理异常声明异常

throws用于进行异常类的声明,若该方法可能有多种异常情况产生,那么在throws后面可以写多个异常类,用逗号隔开。

public class Demo05Throws {
    /*
        FileNotFoundException extends IOException extends Excepiton
        如果抛出的多个异常对象有子父类关系,那么直接声明父类异常即可
     */
    public static void main(String[] args) throws Exception {
        readFile("c:\\a.tx");
        System.out.println("后续代码");
    }
    /*
        定义一个方法,对传递的文件路径进行合法性判断
        如果路径不是"c:\\a.txt",那么我们就抛出文件找不到异常对象,告知方法的调用者
        注意:
            FileNotFoundException是编译异常,抛出了编译异常,就必须处理这个异常
            可以使用throws继续声明抛出FileNotFoundException这个异常对象,让方法的调用者处理
     */
    public static void readFile(String fileName) throws FileNotFoundException,IOException{
        if(!fileName.equals("c:\\a.txt")){
            throw new FileNotFoundException("传递的文件路径不是c:\\a.txt");
        }
        /*
            如果传递的路径,不是.txt结尾
            那么我们就抛出IO异常对象,告知方法的调用者,文件的后缀名不对
         */
        if(!fileName.endsWith(".txt")){
            throw new IOException("文件的后缀名不对");
        }
        System.out.println("路径没有问题,读取文件");
    }
}

4. 捕获异常try…catch

如果出现异常立刻终止程序,所以我们需要处理异常:

  1. 该方法不处理,而是声明抛出,由该方法的调用者来处理(throws)。
  2. 在方法中使用try-catch的语句块来处理异常。

try-catch的方式就是捕获异常,是异常处理的第二种方式,自己处理异常

  • 捕获异常:Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理。

格式:

try{
     编写可能会出现异常的代码
}catch(异常类型  e){
     处理异常的代码
     //记录日志/打印异常信息/继续抛出异常
}
//try:该代码块中编写可能产生异常的代码。
//catch:用来进行某种异常的捕获,实现对捕获到的异常进行处理。

注意:

  • try中可能会抛出多个异常对象,那么就可以使用多个catch来处理这些异常对象
  • 如果try中产生了异常,那么就会执行catch中的异常处理逻辑,执行完毕catch中的处理逻辑,继续执行try...catch之后的代码
  • 如果try中没有产生异常,那么就不会执行catch中异常的处理逻辑,执行完try中的代码,继续执行try...catch之后的代码
  • try和catch都不能单独使用,必须连用

演示如下:

public class Demo01TryCatch {
    public static void main(String[] args) {
        try{
            //可能产生异常的代码
            readFile("d:\\a.tx");
            System.out.println("资源释放");
        }catch (IOException e){//try中抛出什么异常对象,catch就定义什么异常变量,用来接收这个异常对象
            //异常的处理逻辑,异常异常对象之后,怎么处理异常对象
            //System.out.println("catch - 传递的文件后缀不是.txt");

            //System.out.println(e.getMessage());//文件的后缀名不对
            //System.out.println(e.toString());//重写Object类的toString java.io.IOException: 文件的后缀名不对
            //System.out.println(e);//java.io.IOException: 文件的后缀名不对 
            e.printStackTrace();
        }
        System.out.println("后续代码");
    }
    /*
       如果传递的路径,不是.txt结尾
       那么我们就抛出IO异常对象,告知方法的调用者,文件的后缀名不对
    */
    public static void readFile(String fileName) throws IOException {
        if(!fileName.endsWith(".txt")){
            throw new IOException("文件的后缀名不对");
        }
        System.out.println("路径没有问题,读取文件");
    }
}

如何获取异常信息:

Throwable类中定义了一些查看方法

  • public String getMessage(),获取异常的描述信息、原因提示给用户,即返回此异常的简短描述

  • public String toString(),重写Object类的toString方法,获取异常的类型和异常描述信息

  • public void printStackTrace(),JVM打印异常对象,默认此方法,打印的异常信息是最全面

? 包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。

5. finally 代码块

finally:有一些特定的代码无论异常是否发生都需要执行,若因为异常,会引发程序跳转导致有些语句执行不到。而finally就是解决这个问题,在finally代码块中存放的代码都是一定会被执行的。

什么时候的代码必须最终执行?

当我们在try语句块中打开了一些物理资源(磁盘文件/网络连接/数据库连接等),我们都得在使用完之后,最终关闭打开的资源。

格式:

try{
            可能产生异常的代码
        }catch(定义一个异常的变量,用来接收try中抛出的异常对象){
            异常的处理逻辑,异常异常对象之后,怎么处理异常对象
            一般在工作中,会把异常的信息记录到一个日志中
        }
        ...
        catch(异常类名 变量名){

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

注意:

  • finally不能单独使用,必须和try一起使用
  • finally一般用于资源释放(资源回收),无论程序是否出现异常,最后都要资源释放(IO)
  • 如果finally中有return语句程序永远返回finally中的结果。需避免这种情况发生。不要在final语句中使用return

比如在我们之后学习的IO流中,当打开了一个关联文件的资源,最后程序不管结果如何,都需要把这个资源关闭掉。

finally代码参考如下:

public class Demo02TryCatchFinally {
    public static void main(String[] args) {
        try {
            //可能会产生异常的代码
            readFile("c:\\a.tx");
        } catch (IOException e) {
            //异常的处理逻辑
            e.printStackTrace();
        } finally {
            //无论是否出现异常,都会执行
            System.out.println("资源释放");
        }
    }

    /*
       如果传递的路径,不是.txt结尾
       那么我们就抛出IO异常对象,告知方法的调用者,文件的后缀名不对

    */
    public static void readFile(String fileName) throws IOException {

        if(!fileName.endsWith(".txt")){
            throw new IOException("文件的后缀名不对");
        }

        System.out.println("路径没有问题,读取文件");
    }
}

只有在try或者catch中调用退出JVM的相关方法,finally才不会执行,否则会永远执行

6. 异常注意事项

  • 多个异常使用捕获处理方法如下

    1. 多个异常分别处理。
    2. 多个异常一次捕获,多次处理。
    3. 多个异常一次捕获一次处理。

    一般是使用一次捕获多次处理方式,格式如下:

    try{
         编写可能会出现异常的代码
    }catch(异常类型A  e){  当try中出现A类型异常,就用该catch来捕获.
         处理异常的代码
         //记录日志/打印异常信息/继续抛出异常
    }catch(异常类型B  e){  当try中出现B类型异常,就用该catch来捕获.
         处理异常的代码
         //记录日志/打印异常信息/继续抛出异常
    }
    

    注意:这种异常处理方式,要求多个catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。

  • 运行时异常被抛出可以不处理。即不捕获也不声明抛出。

  • 如果finally有return语句,永远返回finally中的结果,避免该情况.

子父类异常

  • 如果父类抛出了多个异常,子类重写父类方法时有三种情况
    • 抛出和父类相同的异常
    • 抛出父类异常的子类
    • 不抛出异常
  • 父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常
    • 此时子类产生该异常,只能捕获处理,不能声明抛出

注意:

  • 父类异常时什么样,子类异常就什么样

三、自定义异常

1. 概述

Java中有很多种异常类,分别表示某一种具体的异常情况。但实际中总是有些异常情况是没有定义好的,此时我们可以根据遇到的的异常情况来定义异常类。例如年龄负数问题,考试成绩负数问题等等。

定义:

  • java提供的异常类不够我们使用,需要自己定义一些异常类
  • 自定义一个编译期异常: 自定义类 并继承于java.lang.Exception
  • 自定义一个运行时期的异常类:自定义类 并继承于java.lang.RuntimeException

格式:

public class XXXExcepiton extends Exception | RuntimeException{
            添加一个空参数的构造方法
            添加一个带异常信息的构造方法
}

注意:

  • 自定义异常类一般都是以Exception结尾,说明该类是一个异常类
  • 自定义异常类,必须的继承Exception或者RuntimeException
    • 继承Exception:那么自定义的异常类就是一个编译期异常,如果方法内部抛出了编译期异常,就必须处理这个异常,要么throws,要么try...catch
    • 继承RuntimeException:那么自定义的异常类就是一个运行期异常,无需处理,交给虚拟机处理(中断处理)

例子:

要求:模拟注册操作,如果用户名已存在,则抛出异常并提示:该用户名已经被注册

分析:

  • 使用数组保存已经注册过的用户名(数据库)
  • 使用Scanner获取用户输入的注册的用户名(前端,页面)
  • 定义一个方法,对用户输入的中注册的用户名进行判断
    • 遍历存储已经注册过用户名的数组,获取每一个用户名
    • 使用获取到的用户名和用户输入的用户名比较
      • true:
        • 用户名已经存在,抛出RegisterException异常,告知用户"该用户名已经被注册";
      • false:
        • 继续遍历比较
        • 如果循环结束了,还没有找到重复的用户名,提示用户"恭喜您,注册成功!";
public class RegisterException {
    // 1.使用数组保存已经注册过的用户名(数据库)
    static String[] usernames = {"张三","李四","王五"};

    public static void main(String[] args) /*throws RegisterException*/ {
        //2.使用Scanner获取用户输入的注册的用户名(前端,页面)
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入您要注册的用户名:");
        String username = sc.next();
        checkUsername(username);

    }
        //3.定义一个方法,对用户输入的中注册的用户名进行判断
    public static void checkUsername(String username) /*throws RegisterException*/ {
        //遍历存储已经注册过用户名的数组,获取每一个用户名
        for (String name : usernames) {
            //使用获取到的用户名和用户输入的用户名比较
            if(name.equals(username)){
                //true:用户名已经存在,抛出RegisterException异常,告知用户"该用户名已经被注册";
                try {
                    throw new RegisterException("该用户名已经被注册");
                } catch (RegisterException e) {
                    e.printStackTrace();
                    return; //结束方法
                }
            }
        }

        //如果循环结束了,还没有找到重复的用户名,提示用户"恭喜您,注册成功!";
        System.out.println("恭喜您,注册成功!");
    }
}

第四章 多线程

Java给多线程编程提供了内置的支持。一个多线程程序包含两个或多个能并发运行的部分。程序的每一部分都称作一个线程,并且每个线程定义了一个独立的执行路径。

  • 多线程能满足程序员编写非常有效率的程序来达到充分利用CPU的目的,因为CPU的空闲时间能够保持在最低限度。

1. 并发与并行

  • 并发:指两个或多个事件在同一个时间段内发生。
  • 并行:指两个或多个事件在同一时刻发生(同时发生)。

注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。

2. 线程与进程

  • 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
  • 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行。
    • 一个线程不能独立的存在,它必须是进程的一部分
    • 一个进程中是可以有多个线程的,这个应用程序也称之为多线程程序。
    • 一个程序运行后至少有一个进程,一个进程中可以包含多个线程

线程调度:

  • 分时调度

    所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

  • 抢占式调度

    优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

3. 创建线程类

Java提供了三种创建线程方法:

  • 通过实现Runnable接口
  • 通过继承Thread类本身
  • 通过 Callable 和 Future 创建线程

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

Java使用java.lang.Thread是描述线程的类,所有的线程对象都必须是Thread类或其子类的实例。我们想要实现多线程程序,就必须继承Thread类

实现步骤:

  • 创建一个Thread类的子类
  • 在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
  • 创建Thread类的子类对象
  • 用Thread类中的方法start方法,开启新的线程,执行run方法
    • void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法
    • 结果是两个线程并发地运行;当前线程(main线程)和另一个线程(创建的新线程,执行其 run 方法)
    • 多次启动一个线程是非法的。特别是当线程已经结束执行后,不能再重新启动

java程序属于抢占式调度,那个线程的优先级高,那个线程优先执行;同一个优先级,随机选择一个执行

代码如下:

测试类:

public class Demo01Thread {
    public static void main(String[] args) {
        //3.创建Thread类的子类对象
        MyThread mt = new MyThread();
        //4.调用Thread类中的方法start方法,开启新的线程,执行run方法
        mt.start();

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

自定义线程类:

//1.创建一个Thread类的子类
public class MyThread extends Thread{
    //2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
    @Override
    public void run() {
        for (int i = 0; i <20 ; i++) {
            System.out.println("run:"+i);
        }
    }
}

异常与多线程(一)

标签:catch   开启   结构图   指针   except   如何获取   管程   优先级   nal   

原文地址:https://www.cnblogs.com/lf-637/p/13096713.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!