Java中的异常处理及线程

一. 异常

  1. 异常体系
    异常的根类是 Throwable ,其子类有 Error 与 Exception, 一般异常是指的 Exception; 严重错误是 Error , Exception 表示异常
    Throwable 中常用的方法:

printStackTrace() : 打印异常的详细信息,包含类型即异常原因,位置和在调试阶段都得使用 printStackTrace;
toString() : 获取异常的类型和异常描述信息(一般不用)
getMassage() 返回关于异常的详细信息,这个消息在 Throwable 类构造函数中初始化
getCause() 返回一个 Throwable 对象代表异常的原因

  1. 异常分类
    一般异常就是指 Exception , 那么就有异常(Exception) 的分类
    编译时异常 : checked异常.
    运行时异常 : runtime异常

二. 异常处理

  1. 抛出异常 throw
    是用各式:

throw new 异常类名(参数);

如:

throw new NullPointerException("要访问的xx数组不存在");
public class ErrorExceptionDemoTwo {
    public static void main(String[] args) {
        int[] arr = {1,2,3,4};
        int index = 4;

        int element = getElement(arr,index);
        System.out.println(element);
        System.out.println("End!");

    }
    public static int getElement(int[] arr,int index){
        if(index<0 || index>arr.length - 1){
            throw new ArrayIndexOutOfBoundsException("超出索引范围!");
        }
        int elemint = arr[index];
        return elemint;
    }
}

输出:
在这里插入图片描述
2. Objects 非空判断

public static T requireNoNull(T obj) : 查看指定引用对象不是 null;

  1. 声明 Throws
    声明异常,将问题标识出来,如果方法内通过 throw 抛出了编译时异常,而且没有补货,name必须童年过 throws 进行声明,让调用者处理; 关键字 throws 运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常;
    声明异常的格式:

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

如:

import javax.print.attribute.standard.PresentationDirection;
import java.io.File;
import java.io.FileNotFoundException;
public class ErrorExceptionDemoThree {
    public static void main(String[] args) throws FileNotFoundException {
        readfile("a1.txt");
    }
    public static void readfile(String path) throws FileNotFoundException{
        if(!path.equals("a.txt")) {
            throw new FileNotFoundException("文件不存在哟!");
        }
    }
}

如果有多个异常就用逗号隔开
4. 捕获异常 try … catch
捕获异常使用 try … catch 语句, 可以把发生异常的代码放到 try{ … } 中, 然后使用 catch 捕获对应的 Exception 及其子类

try{
	//编辑可鞥出现的异常的代码
}catch{
	//处理异常代码块
}

注意:try 与 catch 不能单独使用

import java.util.InputMismatchException;
import java.util.Scanner;

public class ErrorDemoTestOne {
    public static void main(String[] args){
        testone();
    }
    public static void testone(){
        Scanner scanner = new Scanner(System.in);
        System.out.println(">>>>>>>>请输入一个数字<<<<<<<<<");
        try {
            scanner.nextInt();
            System.out.println("运行结束");
        } catch(InputMismatchException c){
            System.out.println("请输入有效数字!");
            testone();
        }
    }
}

输出:
在这里插入图片描述

  1. finally
    finally : 有些代码无论有没有异常发生都需要执行,因为异常会引发程序的跳转,导致有些语句不会执行,那么使用 finally 可以解决该问题, 在 finally 中的代码一定会被执行的;

finally 的语法: try … catch … finally
注意: finally 不能单独使用

import java.util.InputMismatchException;
import java.util.Scanner;

public class ErrorDemoTestOne {
    public static void main(String[] args){
        testone();
    }
    public static void testone(){
        Scanner scanner = new Scanner(System.in);
        System.out.println(">>>>>>>>请输入一个数字<<<<<<<<<");
        try {
            scanner.nextInt();
            System.out.println("运行结束");
        } catch(InputMismatchException c){
            System.out.println("请输入有效数字!");
            testone();
        }finally {
           System.out.println("不管怎样,都会打印这句话的!");
        }
    }
}

输出:
在这里插入图片描述
当只有 try 或者 catch 中调用退出 JVM 相关方法时, 测试 finally 才不会执行,否则finally 永远会执行

  1. 异常注意事项

a. 多个异常分别处理
b. 多个异常一次捕获,多次处理
c. 多个异常一次捕获一次处理

如下格式:

try{
	//编辑可能出现的异常
}catch(异常类型a e){
	//异常处理代码
}catch(异常类型b e){
	//异常处理代码
}

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

运行时异常被抛出可以不处理,即不捕获也不声明抛出;
如果finally 有 return 语句,永远返回 finally 中的结果,避免该情况;
如果父类抛出多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常;
父类方法没有抛出异常,子类重写父类的方法时也没有抛出异常,此时子类产生该异常,只能捕获处理,不能声明抛出;

三、自定义异常

1.自定义异常的意义
a. 在实际开发中会出现更多的异常,在JDK中可能灭有定义过,这是需要自定义异常,如月份不能为负
b. 根据自己业务的异常状况来定义异常类
2. 异常类的定义
a.自定义一个编译期异常,自定义类,并继承于 java.lang.Exception.
b. 自定义一个运行时期的异常类, 自定义类并继承于 java.lang.RuntimeException.
例子:如一个登陆异常:

package Demooen;
//自定义异常,继承Exception类
class RegisterException extends Exception{
    String messages;
    //表示异常
    public RegisterException(String user){
        messages = user + " 该用户名名已被注册,请重新注册!";
    };
    public String getMessages() {
        return messages;
    }
}
class Users {
	//模拟已有用户
    private static String[] usernames = {"ll12345", "asd23414", "dfh23413"};
    public void checkusername(String unuers) throws RegisterException {
        for (String username : usernames) {
            if(username.equals(unuers)) { //遍历用户,并对比是否存在
                    throw new RegisterException(unuers); //方法抛出异常类,导致方法结束
                }
        }
    }
}
public class RegisterDemo {
    public static void main(String[] args){
        Users use = new Users();
        try {
            use.checkusername("ll12345");
            System.out.println("注册成功");
        }catch (RegisterException e){
            System.out.println(e.getMessages());
        }
    }
}

输出:
在这里插入图片描述

四、多线程

  1. 并发与并行
    并发:指两个或多个事件在同一时间段内发生;
    并行:指两个或多个事件在同一时刻发生(同时发生)。
    在操作系统中,安装了多个程序,并发指的是在一段时间按内宏观上有多个程序同时运行,在单CPU系统中,每一时刻只能有一道程序执行,即在微观上这些程序是分时的交替运行,只不过个人的感觉是同时运行的,那是因为分时交替运行的时间非常短;
    而在多CPU系统中, 则这些可以并发执行的程序可以分配到多个处理器上, 实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序没这样多个程序便可以同时执行
    注意:单核处理器的计算机是不能并行的处理多个任务们只能是多个任务在单个 CPU 上并发运行,同理县城也一样,从宏观角度上理解线程是并行运行的,但是从微观角度上分析确是串行运行的,即一线程一个线程的运行, 当系统只有一个CPU 时,线程会以某种顺序执行多个线程, 我们把这种情况称之为线程调度.

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

  3. 创建线程类
    Java 使用 Thread 类代表程序,所有的线程对象都必须是 Thread 类或其子类的实例,每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。 Java 使用线程执行体来待变这段程序流,Java 中通过继承 Thread 类创建并启动多线程:
    a. 定义Thread 类的子类,并重写该类的run() 方法,该run方法体就代表了线程需要完成的任务,因此把 run() 方法称之为线程执行体
    b. 创建Thread 子类实例
    c.调用线程对象的 start() 方法来启动给线程

如:

public class ThreadDemo {
    public static void main(String[] args){
        //创建自定义线程对象
        ThreadTest t = new ThreadTest("新线程!");
        //使用 start()开启新线程
        t.start();
        for (int i=0; i<10; i++){
            System.out.println("main线程!"+i);
        }
    }
}
//自定义对象类,并继承 Thread
class ThreadTest extends Thread{
    //定义构造线程名称
    public ThreadTest(String name) {
        //调用父类的String参数的构造方法,指定线程的名称
        super(name);
    }
    //重写run() 方法
    @Override
    public void run(){
        for (int i=0; i<10; i++){
            System.out.println(getName() + "执行中!" + i);
        }
    }
}

五、线程

  1. Thread 类
    构造方法:

Thread() : 分配一个新的线程对象
Thread(String name): 分配一个带有指定名字的新的线程对象
Thread(Runnable target) : 分配一个带有指定目标新的线程对象
Thread(Runnable target,String name) : 分配一个带有指定目标新的线程对象并指定名字

常用方法:

getName(): 获取当前线程组名称
start() : 使此线程组执行,Java 虚拟机调用此线程的 run 方法
run() : 此线程组执行的任务再此定义代码
sleep(long millis) : 是当前正在执行的线程以指定的毫秒数暂停(这哪是停止执行)
Thread currentThread() : 返回对当前正在执行的线程对象的引用

创建线程的方式二
Runnable 是非常常见的一种,只需要重写 run 方法
a. 定义 Runnable 接口的实现类,并重写改接口的 run() 方法,该 run方法的方法体同样是该线程的线程执行体
b. 创建 Runnable 实现类的实例,并以此实例作为 Thread 的 targe 来创建 Thread 对象,该Thread对象才是正真的线程对象
c. 调用线程对象的 start() 方法来启东线程

如:

public class RunnableThreadDemo {
    public static void main(String[] args){
        //创建自定义类对象 线程任务对象
        RunnableTest rt = new RunnableTest();
        //创建线程对象
        Thread t = new Thread(rt,"小强");
        t.start();
        for(int i=0; i<10; i++){
            System.out.println("小李子" + i);
        }
    }
}
class RunnableTest implements Runnable{
    @Override
    public void run(){
        for (int i = 0; i < 10; i++){
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}
  1. Thread 和 Runnable 的区别
    如果一个类继承Thread,则不适合资源共享,但是如果实现了 Runnable 接口的话免责很容易的实现资源的共享.
    实现 Runnable 接口比继承 Thread 类具有优势: a 适合多个相同的程序代码的线程去共享同一个资源
    b. 可以避免java 中单继承的局限性
    c. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享代码和线程独立
    d. 线程池只能放入实现 Runnable 或 Callable 类线程, 不能直接放入继承 Thread 的类

  2. 匿内部类方式实现现成的创建
    使用线程内匿名内部类方式,可以方便的实现每个线程执行不同的线程任务操作,使用匿名内部类的方式实现 Runnable 接口,重写 Runnable 接口中的 run 方法:

public class RunnableTestTwo {
    public static void main(String[] args){
        Runnable run = new Runnable() {
            @Override
            public void run() {
                for (int i=0; i<5; i++){
                    System.out.println("小李子 " + i);
                }
            }
        };
        new Thread(run).start();
        for (int i=0; i<5; i++){
            System.out.println("小强 " + i);
        }
    }
}

六、线程安全

  1. 线程安全
    如果有多个线程在同时运行,而这些线程可能会同时运行这段代码, 程序每次运行结果和单线程运行的结果是一样的,而且其他变量的值也是和预期的一样的,就是线程安全的; 线程安全问题是有全局变量及静态变量引起的, 若每个线程中对全局变量、静态变量只有读操作 ,而无写操作,一般来说,这个全局变量是线程安全的;说有多个线程同时执行写操作,一般需要考虑线程同步,否则影响线程安全。

  2. 线程同步
    当使用多线程访问同一资源时,且多个线程中对资源有写的操作,就容易出现线程安全问题,要解决多线程访问一个资源的安全性问题,Java 中提供了同步机制 (synchronized)来解决
    三个步骤操作
    a. 同步代码块
    b. 同步方法
    c. 锁机制

2.1 同步代码款
同步代码块: synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥fangwen
格式

synchronized(同步锁){
	需要同步操作的代码
}

同步锁: 对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁; 锁对象可以是任意类型; 多个线程对象要使用同一把锁
注意P:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等待(BLOCKED)

如:模拟多窗口只能卖100张票


import java.util.Objects;

public class RunnableTicketsDemo {
    public static void main(String[] args){
        Ticket ticket = new Ticket();
        //创建三个窗口
        Thread t1 = new Thread(ticket, "窗口一");
        Thread t2 = new Thread(ticket, "窗口二");
        Thread t3 = new Thread(ticket, "窗口三");

        //同时买票
        t1.start();
        t2.start();
        t3.start();
    }
}

class Ticket implements Runnable{
    private int tickets = 100;
    Object lock = new Object();
    //执行买票操作
    @Override
    public void run(){
        //窗口永久开启
        while(true){
            synchronized (lock) { //同步代码块,不做同步可能会出现重复
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //获取当前线程组对象名字
                    String name = Thread.currentThread().getName();
                    System.out.println(name + "正在卖: " + tickets--);
                }
            }
        }
    }
}

输出:
在这里插入图片描述
2.2. 同步方法
同步方法:使用synchronized 修饰的方法,就叫做同步方法,保证a 线程执行该方法,其他线程只能在方法外面等着
格式

public synchronized void method(){
//	可能会产生线程安全问题的代码	
}

什么时同步锁: 对于非static 方法,同步锁就是 this ; 对于 static 方法,使用当前方法所在类的字节码对象(类名.class)

...skip...
class Tickets implements Runnable{
    private int tickets = 100;
    //执行买票操作
    @Override
    public void run(){
        //窗口永久开启
        while(true){
            selltickets();
        }
    }
    public synchronized void selltickets(){ //同步方法
        if (tickets > 0) {
            //sleep 模拟出票等待时间
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //获取当前线程组对象名字
            String name = Thread.currentThread().getName();
            System.out.println(name + "正在卖: " + tickets--);
        }
    }
}

2.3 Lock 锁
java.util.concurrent.locks.Lock 机制提供了比 synchronized 代码块和 synchronized方法更广泛的锁定操作, 同步代码块和同步方法具有的功能 Lock 都有,除此之外体现面向对象
Lock 锁称同步锁:
lock() : 加同步锁
unlock : 释放同步锁

...skip...
class Ticketsss implements Runnable{
    private int tickets = 100;
    Lock lock = new ReentrantLock();
    //执行买票操作
    @Override
    public void run(){
        //窗口永久开启
        while(true){
            lock.lock();
            if (tickets > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //获取当前线程组对象名字
                String name = Thread.currentThread().getName();
                System.out.println(name + "正在卖: " + tickets--);
            }
            lock.unlock();
        }
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值