java异常框架||java多线程

Java异常

1 运行时异常与一般异常有何异同

答:。java编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误。运行时期异常一旦发生,需要程序人员修改源代码.(这些异常通常是由于逻辑错误引起的)一般异常(编译时异常):必须进行处理的异常,如果不处理,程序就不能编译通过,可以通过try..catch处理或用throws声明继续抛给上层调用方法处理

运行时异常,我们可以不处理。当出现这样的异常时,总是由虚拟机接管。 RuntimeException 体系包括错误的类型转换、数组越界访问和试图访问空指针等等。假如出现 RuntimeException,那么一定是程序员的错误。

一般异常:定义方法时必须声明所有可能会抛出的异常;在调用这个方法时,可以捕获它的异常,还能把它的exception传递下去;

 

2 Java中的异常处理机制的简单原理和应用

答:所有异常的根类是Throwable,Throwable又派生了两个子类:Error和Exception,Exception包括运行时期异常和编译期异常。Error错误通常没有具体的处理方式,程序将会结束运行。java为系统异常和普通异常提供了不同的解决方案(捕获、往上抛出 ),编译器强制普通异常必须try..[catch..finaly]处理或throws声明继续抛给上层调用方法处理。所以普通异常为checked异常,而系统异常可以处理也可以不处理。编译器不强制用try..catch或throws声明,所以系统异常成为uncheckde异常。

①java异常处理用到了多态的概念,如果在异常处理过程中,先捕获了基类,然后在捕获子类,那么捕获子类的代码块永远不会执行.因此,在进行异常捕获时,正确的写法是先捕获子类,再捕获父类的异常信息

②异常能处理就处理,不能处理就抛出.对于一般异常,如果不能进行行之有效的处理,最好转换成运行时异常抛出

 

 

3 JAVA语言如何进行异常处理,关键字:throws,throw,try,catch,finally分别代表什么意义?在try块中可以抛出异常吗?

答:异常处理一共有两种方式:第一种不处理异常,让其往上抛。第二种就是try。。catch。。。finaly捕获异常处理。

throws是可以获取方法体中一个至多个异常,throws是方法可能抛出异常的声明。

throw是抛出一个异常对象

try是将会发生异常的语句括起来,从而进行异常的处理,

catch是如果有异常就会执行他里面的语句

finally不论是否有异常都会进行执行的语句。。return;也不能让finally停止执行,可以关闭jvm主线程停止执行finally(System.exit(1))

try中可以抛出一个异常:通过throw new XXXException("xxxxx");

 

 

 

4 编写程序举例抛出一个空指针异常、类型转换异常、数组索引越界异常、字符串索引越界异常

复制代码
package Demo1;
​
public class Student {
    int score;
    int age;
    String name;
​
    Computer computer;
​
    public void study() {
​
        System.out.println("studying...");
    }
​
    public static void main(String[] args) {
        Student student = new Student();
        student.score = 90;
        int[] arr = {1, 2, 3};
        String str = "hello";
        student = null;
​
        try {
            System.out.println(str.charAt(5));//字符串索引越界异常
            System.out.println(arr[5]);//数组小标越界异常
            int x = Integer.parseInt("ok");//类型转换异常
            System.out.println(student.score);//程序出现空指针异常
​
        } catch (NullPointerException e) {
            System.out.println("程序出现空指针异常!!!");
        } catch (NumberFormatException e) {
            System.out.println("类型转换异常");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("数组小标越界异常");
        } catch (StringIndexOutOfBoundsException e) {
            System.out.println("字符串索引越界异常");
        }
​
​
    }
}
复制代码

线程创建方式一

继承Thread

  1. 创建一个继承于Thread类的子类

  2. 重写Thread类的run() --> 将此线程执行的操作声明在run()中

  3. 创建Thread类的子类的对象

  4. 通过此对象调用start()

注意点:

  1. 如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。

  2. run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU 调度决定。

  3. 想要启动多线程,必须调用start方法。

  4. 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上 的异常IllegalThreadStateException

课堂练习

创建两个分线程,让其中一个线程输出1-100之间的偶数,另一个线程输出1-100之间的奇数。

Thread类常用方法

  • void start(): 启动线程,并执行对象的run()方法

  • run(): 线程在被调度时执行的操作

  • String getName(): 返回线程的名称

  • void setName(String name):设置该线程名称

  • static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类

  • static void yield():线程让步

    • 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程

    • 若队列中没有同优先级的线程,忽略此方法

  • join() 当某个程序执行流中调用其他线程的 join() 方法时,调用线程将 被阻塞,直到 join() 方法加入的 join 线程执行完为止低优先级的线程也可以获得执行

  • static void sleep(long millis):(指定时间:毫秒)

    • 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后 重排队。

    • 抛出InterruptedException异常

  • stop(): 强制线程生命期结束,不推荐使用

  • boolean isAlive():返回boolean,判断线程是否还活着

 线程调度(优先级)

Java的调度方法

  • 同优先级线程组成先进先出队列(先到先服务),使用时间片策略

  • 对高优先级,使用优先调度的抢占式策略

线程的优先级等级

  • MAX_PRIORITY:10

  • MIN _PRIORITY:1

  • NORM_PRIORITY:5

涉及的方法

  • getPriority():返回线程优先值

  • setPriority(int newPriority):改变线程的优先级

说明

  • 线程创建时继承父线程的优先级

  • 低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

线程创建方式二

实现Runnable接口

  1. 创建一个实现了Runnable接口的类

  2. 实现类去实现Runnable中的抽象方法:run()

  3. 创建实现类的对象

  4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象

  5. 通过Thread类的对象调用start()

 两种方式比较

区别

  • 继承Thread:线程代码存放Thread子类run方法中。

  • 实现Runnable:线程代码存在接口的子类的run方法。

实现方式的好处

  • 避免了单继承的局限性

  • 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。

线程的生命周期

 

 

 

Synchronized关键字

使用synchronized关键字可以解决线程同步问题

使用方式

  • 同步代码块:

synchronized (对象){
//需要被同步的代码;
}
  • 同步方法

    • synchronized还可以放在方法声明中,表示整个方法为同步方法。

public synchronized void show (String name){  
//需要被同步的代码;
}

关于的概念

  • 同步锁机制:

    • 在《Thinking in Java》中,是这么说的:对于并发工作,你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。 防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而在其被解锁 之时,另一个任务就可以锁定并使用它了。

  • synchronized的锁是什么?

    • 任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。

    • 同步方法的锁:静态方法(类名.class)、非静态方法(this)

    • 同步代码块:自己指定,很多时候也是指定为this或类名.class

  • 注意:

    • 必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就无法保证共享资源的安全

    • 一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this),同步代码块(指定需谨慎)

同步的范围

  • 如何找问题,即代码是否存在线程安全?(非常重要)

    1. 明确哪些代码是多线程运行的代码 run()

    2. 明确多个线程是否有共享数据

    3. 明确多线程运行代码中是否有多条语句操作共享数据

  • 如何解决呢?(非常重要)

    • 对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。

    • 即所有操作共享数据的这些语句都要放在同步范围中

  • 切记:

    • 范围太小:没锁住所有有安全问题的代码

    • 范围太大:没发挥多线程的功能。

Lock方式

  • 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。

  • java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

  • ReentrantLock类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

 

同步代码块中涉及到同步监视器和共享数据,谈谈你对他们的理解:

答:监视器一般我们说成同步锁。任意一个类的实例对象都可以充当这锁。但是为了保证多线程数据是共享操作的,锁一定要同一把(同一个对象)才行。不过同步锁比较麻烦,不能锁多了逻辑块,也不能锁少了,不然达不到目的。同步方法默认锁的是this,当前调用者自己。extends实现多线程有时候比较鸡肋。

锁对象我们一般使用两个:

  1. this

  2. 同一操做目标类的字节码文件:XXX.class || this.getClas()(不可靠)

除了使用Synchronized关键字,从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)。一般使用Lock接口的实现类:ReentrantLock ;

共享数据:多个线程共同操作的数据,即为共享数据。它可以被任意一个线程所操作而改变。

 

创建多线程有哪几种方式

答:

  1. 继承Thread类实现多线程

  2. 实现Runnable接口

  3. 实现Callable接口

  4. 线程池

通讯方法

  • wait()与notify()和notifyAll()

    • wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。

    • notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待

    • notifyAll ():唤醒正在排队等待资源的所有线程结束等待.

  • 这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报 java.lang.IllegalMonitorStateException异常。

  • 因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object类中声明。

wait()方法

  • 在当前线程中调用方法: 对象名.wait()

  • 使当前线程进入等待(某对象)状态 ,直到另一线程对该对象发出 notify (或notifyAll) 为止。

  • 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)

  • 调用此方法后,当前线程将释放对象监控权 ,然后进入等待

  • 在当前线程被notify后,要重新获得监控权,然后从断点处继续代码的执行。

notify()/notifyAll()

  • 在当前线程中调用方法: 对象名.notify()

  • 功能:唤醒等待该对象监控权的一个/所有线程。

  • 调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)

线程实现的其他方式

 实现Callable接口

  • 与使用Runnable相比, Callable功能更强大些

    • 相比run()方法,可以有返回值

    • 方法可以抛出异常

    • 支持泛型的返回值

    • 需要借助FutureTask类,比如获取返回结果

  • Future接口

    • 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。

    • FutrueTask是Futrue接口的唯一的实现类

    • FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

 

 

复制代码
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 创建线程的方式三:实现Callable接口。 --- JDK 5.0新增
 *
 *
 * 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
 * 1. call()可以有返回值的。
 * 2. call()可以抛出异常,被外面的操作捕获,获取异常的信息
 * 3. Callable是支持泛型的
 *
 */
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
    //2.实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if(i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}


public class ThreadNew {
    public static void main(String[] args) {
        //3.创建Callable接口实现类的对象
        NumThread numThread = new NumThread();
        //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(numThread);
        //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
        new Thread(futureTask).start();

        try {
            //6.获取Callable中call方法的返回值
            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
            Object sum = futureTask.get();
            System.out.println("总和为:" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

}
复制代码

使用线程池

  • 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

  • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

  • 好处:

    • 提高响应速度(减少了创建新线程的时间)

    • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)

    • 便于线程管理

      • corePoolSize:核心池的大小

      • maximumPoolSize:最大线程数

      • keepAliveTime:线程没有任务时最多保持多长时间后会终止

  • JDK 5.0起提供了线程池相关API:ExecutorServiceExecutors

  • ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

    • void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable

    • <T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般又来执行Callable

    • void shutdown() :关闭连接池

  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

    • Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池

    • Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池

    • Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池

    • Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

复制代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 创建线程的方式四:使用线程池
 *
 * 好处:
 * 1.提高响应速度(减少了创建新线程的时间)
 * 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
 * 3.便于线程管理
 *      corePoolSize:核心池的大小
 *      maximumPoolSize:最大线程数
 *      keepAliveTime:线程没有任务时最多保持多长时间后会终止
 *
 *
 * 面试题:创建多线程有几种方式?四种!
 */

class NumberThread implements Runnable{

    @Override
    public void run() {
        for(int i = 0;i <= 100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

class NumberThread1 implements Runnable{

    @Override
    public void run() {
        for(int i = 0;i <= 100;i++){
            if(i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

public class ThreadPool {

    public static void main(String[] args) {
        //1. 提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        //设置线程池的属性
//        System.out.println(service.getClass());
//        service1.setCorePoolSize(15);
//        service1.setKeepAliveTime();


        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread());//适合适用于Runnable
        service.execute(new NumberThread1());//适合适用于Runnable

//        service.submit(Callable callable);//适合使用于Callable
        //3.关闭连接池
        service.shutdown();
    }

}
复制代码

 

posted on   白嫖老郭  阅读(209)  评论(0编辑  收藏  举报

编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示