likes
comments
collection
share

多线程自测(一)

作者站长头像
站长
· 阅读数 17

提前声明:该内容大部分为chatgpt生成的,测试一下使用他来生成答案的能力。暂时没有发现问题。

有多少种实现线程的方法?

有4中方法

  1. 继承Thread类:您可以创建一个继承自Thread类的子类,并重写run()方法,在run()方法中定义线程的逻辑。然后可以创建该子类的对象,并调用start()方法启动线程。

示例代码:

class MyThread extends Thread {
    public void run() {
        // 线程逻辑
    }
}

// 创建并启动线程
MyThread thread = new MyThread();
thread.start();
  1. 实现Runnable接口:您可以创建一个实现了Runnable接口的类,并实现其run()方法,在run()方法中定义线程的逻辑。然后可以创建该类的对象,并将其作为参数传递给Thread类的构造函数,最后调用start()方法启动线程。

示例代码:

class MyRunnable implements Runnable {
    public void run() {
        // 线程逻辑
    }
}

// 创建并启动线程
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
  1. 使用Executor框架:Java提供了Executor框架,它抽象了线程的创建和管理,可以方便地执行多个任务。可以使用ExecutorService接口的实现类来创建线程池,并提交任务执行。

示例代码:

ExecutorService executor = Executors.newFixedThreadPool(5); // 创建包含5个线程的线程池

Runnable task = new Runnable() {
    public void run() {
        // 线程逻辑
    }
};

executor.execute(task); // 提交任务给线程池执行
  1. 使用Callable和Future:如果需要在线程执行完成后获取返回结果,可以使用Callable接口代替Runnable接口,并使用Future来获取执行结果。

示例代码:

ExecutorService executor = Executors.newSingleThreadExecutor();

Callable<String> task = new Callable<String>() {
    public String call() throws Exception {
        // 线程逻辑
        return "Hello";
    }
};

Future<String> future = executor.submit(task);
String result = future.get(); // 获取线程执行结果

System.out.println(result); // 输出结果:"Hello"

实现Runnable接口和继承Thread类哪种方式更好?

实现Runnable接口和继承Thread类这两种方式都可以用来创建线程,它们各有优缺点,取决于具体需求和设计考虑。

  1. 实现Runnable接口:
  • 优点:
    • 避免了Java单继承的限制,因为在Java中,一个类只能继承自一个父类,但是可以实现多个接口。
    • 可以更好地利用面向对象的设计原则,将线程的任务逻辑与线程本身分离,提高代码的可维护性和复用性。
    • 可以方便地通过线程池执行多个任务,共享线程资源。
  • 缺点:
    • 实现Runnable接口需要实现run()方法,而run()方法没有返回值,无法直接获取线程的执行结果。如果需要获取结果,需要通过其他方式实现,如使用共享变量或使用Future。
  1. 继承Thread类:
  • 优点:
    • 相对较为简单,适合编写简单的线程逻辑。
    • 可以重写Thread类的其他方法,例如构造函数、start()方法等。
  • 缺点:
    • 继承Thread类后,就无法再继承其他类,因为Java不支持多重继承。
    • 由于线程的任务逻辑与线程本身耦合在一起,可维护性和复用性较差。

综上所述,如果需要简单地创建一个线程,实现某些简单的任务逻辑,继承Thread类可能更为方便。但如果需要更好地组织和管理线程,希望通过线程池执行任务,或者遵循面向对象的设计原则,将任务逻辑与线程本身解耦,那么实现Runnable接口是更好的选择。

一个线程两次调用start()方法会出现什么情况?为什么?

当一个线程已经启动并处于活动状态时,如果再次调用该线程的start()方法,会抛出IllegalThreadStateException异常。 线程在第一次调用start()方法后进入活动状态,开始执行线程的任务逻辑。在这个过程中,线程会执行run()方法中定义的代码。当线程的任务逻辑执行完毕或被中断时,线程将终止并进入终止状态。 当我们尝试对已经进入终止状态的线程再次调用start()方法时,由于线程已经无法重新启动,Java虚拟机会抛出IllegalThreadStateException异常。这是因为线程的生命周期已经结束,无法再次启动。每个线程对象只能被启动一次。 因此,为了创建并启动一个新的线程,我们需要创建一个新的线程对象,并调用它的start()方法。

既然start()方法会调用run()方法,为什么我们选择调用start()方法,而不是直接调用run()方法呢?

start()方法是Thread类提供的方法,用于启动一个新线程并让其进入可运行状态,以便在适当的时候由操作系统调度执行。start()方法会执行一些必要的准备工作,包括为线程分配资源、将线程置于就绪状态等。然后,操作系统将选择适当的时机来执行线程,并调用线程的run()方法。 直接调用run()方法只会在当前线程中按照普通的方法调用来执行线程的任务逻辑,而不会创建一个新的线程。这样做的结果是,任务逻辑会在当前线程中同步执行,而不会并发执行。如果我们只是调用run()方法而不使用多线程,那么线程的概念就失去了意义。 使用start()方法可以利用多核处理器和操作系统的并发机制,让多个线程可以并发执行,提高系统的响应性能和并行处理能力。当我们调用start()方法时,操作系统会负责创建一个新的线程,并在合适的时机启动该线程,以便与其他线程并发执行。 因此,为了真正实现多线程的并发执行,我们应该使用start()方法来启动线程,而不是直接调用run()方法。

如何正确停止一个线程?

一般使用interrupt()方法来实现线程的中断。

class MyThread extends Thread {
    public void run() {
        while (!Thread.interrupted()) {
            // 线程任务逻辑

            // 在适当的位置检查中断状态,决定是否继续执行任务逻辑
            if (Thread.interrupted()) {
                // 处理中断操作
                break;
            }
        }
    }
}

当调用线程的interrupt()方法时,会向目标线程发送中断信号。线程可以通过检查自身的中断状态来判断是否被中断,并在适当的时机终止执行。在任务逻辑中,你可以在合适的位置插入对中断状态的检查,以实现及时的线程停止。 如果线程可能会阻塞的话,我们可以使用interrupt()方法中断线程。这会向线程发送中断信号,线程可以通过检查中断状态来相应中断并终止执行。在阻塞操作中,如果线程处于可中断状态(如Thread.sleep()Object.wait()Thread.join()等),中断操作会抛出InterruptedException异常,可以在异常处理中进行线程的停止逻辑。 例如:

Thread thread = new Thread(() -> {
    try {
        while (!Thread.currentThread().isInterrupted()) {
            // 执行阻塞操作
            // ...
        }
    } catch (InterruptedException e) {
        // 线程中断异常处理
        Thread.currentThread().interrupt(); // 重新设置中断状态
    }
});

// 在需要停止线程的地方调用
thread.interrupt();

对于每次 循环都有执行阻塞操作的情况其实可以省去Thread.currentThread().isInterrupted()这个判断,直接用捕获InterruptedException来处理这个中断请求。 Thread.sleep()中断之后会清除标记位,所有一定要手动设置中断状态。

响应中断方法

  • Object.wait
  • Thread.sleep
  • Thread.join
  • java.util.concurrent.BlockingQueue.take()/put()
  • java.util.concurrent.locks.Lock.lockInterruptibly()
  • java.util.concurrent.CountDownLatch.await()
  • java.util.concurrent.CyclicBarrier.await()
  • java.util.concurrent.Exchanger.exchange()
  • java.nio.channels.InterruptibleChannel相关方法
  • java.nio.channels.Selector相关方法

如何处理不可中断阻塞

想办法让代码能相应中断。

线程有哪几种状态,生命周期是什么

  1. 新建(New):当通过new Thread()创建一个线程对象时,线程处于新建状态。此时线程还没有开始运行。
  2. 可运行(Runnable):线程进入可运行状态后,可以被线程调度器选择为当前可运行线程,并开始执行线程的run()方法。在可运行状态中,线程可能正在执行,也可能在等待系统资源(如CPU时间片)。
  3. 运行(Running):线程处于运行状态时,正在执行其run()方法中的代码。线程可能通过抢占式的线程调度机制获得CPU时间片并执行任务。
  4. 阻塞(Blocked):线程进入阻塞状态时,因为某些原因暂时停止执行。常见的阻塞原因包括等待获取锁、等待I/O操作完成、等待其他线程的通知等。当满足特定条件时,线程可以从阻塞状态中恢复并重新进入可运行状态。
  5. 等待(Waiting):线程进入等待状态时,线程暂停执行,并等待其他线程的通知或特定条件的满足。线程需要等待其他线程调用notify()、notifyAll()或signal()等方法来唤醒或满足等待条件。
  6. 计时等待(Timed Waiting):与等待状态类似,但可以设置一个超时时间。线程在计时等待状态中暂停执行,并等待其他线程的通知或超时时间的到达。
  7. 终止(Terminated):当线程的run()方法执行完毕或线程发生了未捕获的异常而终止时,线程进入终止状态。一旦线程进入终止状态,它就无法再切换到其他状态。

如何用wait()实现两个线程交替答应0~100的奇偶数

class NumberPrinter {
    private int number = 0;
    private final Object lock = new Object();

    public void printOddNumber() {
        synchronized (lock) {
            while (number <= 100) {
                if (number % 2 == 0) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    System.out.println("Odd: " + number);
                    number++;
                    lock.notify();
                }
            }
        }
    }

    public void printEvenNumber() {
        synchronized (lock) {
            while (number <= 100) {
                if (number % 2 == 1) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    System.out.println("Even: " + number);
                    number++;
                    lock.notify();
                }
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        NumberPrinter printer = new NumberPrinter();

        Thread oddThread = new Thread(printer::printOddNumber);
        Thread evenThread = new Thread(printer::printEvenNumber);

        oddThread.start();
        evenThread.start();
    }
}

如何用wait实现生产者模式

使用wait()notify()方法来实现生产者模式,主要思路就是生产者线程向共享缓冲区中生产数据,以供消费者线程消费。

class Buffer {
    private List<Integer> data = new ArrayList<>();
    private int capacity;

    public Buffer(int capacity) {
        this.capacity = capacity;
    }

    public void produce() {
        synchronized (this) {
            while (data.size() >= capacity) {
                try {
                    wait(); // 等待消费者消费数据
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            int newData = generateData(); // 生成新的数据
            data.add(newData);
            System.out.println("Produced: " + newData);

            notify(); // 通知消费者可以消费数据了
        }
    }

    public void consume() {
        synchronized (this) {
            while (data.isEmpty()) {
                try {
                    wait(); // 等待生产者生产数据
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            int consumedData = data.remove(0); // 消费数据
            System.out.println("Consumed: " + consumedData);

            notify(); // 通知生产者可以生产数据了
        }
    }

    private int generateData() {
        // 生成数据的逻辑
        return new Random().nextInt(100);
    }
}

public class Main {
    public static void main(String[] args) {
        Buffer buffer = new Buffer(5);

        Thread producerThread = new Thread(() -> {
            while (true) {
                buffer.produce();
                sleepRandom();
            }
        });

        Thread consumerThread = new Thread(() -> {
            while (true) {
                buffer.consume();
                sleepRandom();
            }
        });

        producerThread.start();
        consumerThread.start();
    }

    private static void sleepRandom() {
        try {
            Thread.sleep(new Random().nextInt(1000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

为什么wait()必须在同步代码块中使用

wait()方法必须在同步代码块中使用的原因是,它依赖于对象的监视器(或锁)机制。 当一个线程执行同步代码块时,它会获取对象的监视器(锁),其他线程在同一对象上执行同步代码块时会被阻塞,直到获取到监视器。在同步代码块内部,线程可以调用wait()方法来释放监视器并进入等待状态,同时允许其他线程获取监视器并执行相关操作。 如果wait()方法不在同步代码块中调用,那么线程就无法释放对象的监视器,其他线程无法获取到监视器,从而无法执行相应的操作。此外,wait()方法还依赖于监视器的通知机制(通过notify()notifyAll()方法),以便在合适的时机唤醒等待中的线程。只有在获取到监视器的线程才能够调用notify()notifyAll()方法来唤醒等待线程。 因此,为了正确使用wait()方法,需要在同步代码块中调用,以便正确释放监视器并允许其他线程获取监视器执行相应操作,同时依赖于监视器的通知机制来唤醒等待中的线程。这样可以确保线程之间的正确协作和同步。

为什么线程通信的方法wait(),notify和notifyAll()被定义在Object类里?而sleep定义在Thread类里?

wait(), notify(), 和 notifyAll() 方法被定义在 Object 类中,而不是 Thread 类中的原因是因为线程通信是基于对象之间的同步机制进行的,而不仅限于线程本身。 在Java中,每个对象都有一个关联的锁(或称为监视器)。线程可以通过获取对象的锁来进入同步块或同步方法,并与其他线程进行同步。当一个线程调用了对象的 wait() 方法时,它会释放该对象的锁,并进入等待状态,直到其他线程通过调用相同对象的 notify()notifyAll() 方法来通知它。这样,线程之间可以通过对象的锁进行协作和通信。 因为对象的锁和同步机制是用于线程之间的通信和协作的基础,所以 wait(), notify(), 和 notifyAll() 方法被定义在所有对象的顶级父类 Object 中,以确保所有对象都具有这些基本的线程通信功能。 而 sleep() 方法则是 Thread 类的一部分,因为它是直接与线程相关的操作。sleep() 方法用于让当前线程进入暂停状态,暂停一段指定的时间。这是一种线程级别的操作,不涉及对象之间的同步或通信。因此,sleep() 方法被定义在 Thread 类中。 总结起来,wait(), notify(), 和 notifyAll() 方法是用于对象之间的线程通信和协作,而 sleep() 方法是用于线程的暂停和等待。它们被定义在不同的类中,以反映它们在多线程编程中的不同角色和用途。

wait方式是属于Obejct对象的,那调用Thread.wait()会怎么样

调用 Thread.wait() 方法会导致 IllegalMonitorStateException 异常。 wait() 方法是定义在 Object``类中的,它需要在同步代码块或同步方法中使用,并且是在调用对象的实例上进行操作。因为 ThreadObject的子类,所以Thread类也继承了wait()方法。 然而,在调用 Thread.wait()方法时,实际上是在当前线程对象上调用 wait()方法,而不是在线程所代表的线程上进行操作。这样会导致在 `Thread对象上调用 wait()``方法时,没有持有正确的锁对象(即当前线程对象本身并不是锁对象),从而导致 IllegalMonitorStateException异常的抛出。 因此,应该避免在Thread对象上直接调用 `wait()方法。正确的做法是在同步代码块或同步方法中使用 wait()方法,并在合适的对象上调用它,以确保正确的锁对象和线程间的通信与协作。

选择使用notify()还是notifyAll()

在选择使用 notify() 还是 notifyAll() 方法时,取决于你的需求和设计考虑。 notify() 方法用于唤醒在对象的等待池中等待的单个线程。如果多个线程在等待池中等待同一个对象的锁,并且只有一个线程需要被唤醒,那么使用 notify() 方法是合适的选择。 notifyAll() 方法用于唤醒在对象的等待池中等待的所有线程。如果多个线程在等待池中等待同一个对象的锁,并且所有等待的线程都需要被唤醒,那么使用 notifyAll() 方法更为适合。 选择使用 notify() 还是 notifyAll() 取决于你希望唤醒多少个等待线程。如果只需要唤醒一个线程,使用 notify() 更高效。但是,如果需要唤醒所有等待的线程,使用 notifyAll() 更为安全,以确保不会有任何线程被遗漏。 需要注意的是,在使用 notify()notifyAll() 方法之后,等待线程的唤醒顺序是不确定的,因为这取决于线程调度器的具体实现。因此,在设计线程间的通信和协作时,应该注意避免对线程的执行顺序做出假设,而是依赖于适当的同步和互斥机制来保证正确性和可靠性。

notifyAll()之后所有的线程都会再次抢夺锁,如果某线程抢夺失败怎么办

在使用 notifyAll() 方法之后,所有等待的线程将被唤醒并开始竞争对象的锁。如果某个线程在竞争锁的过程中失败,你可以使用循环结构来重新尝试获取锁,直到成功获取锁或满足退出条件。 下面是一个示例代码片段,演示了在使用 notifyAll() 后重新尝试获取锁的过程:

synchronized (lock) {
    while (!condition) {
        try {
            lock.wait();
        } catch (InterruptedException e) {
            // 处理中断异常
        }
    }
    // 执行需要锁保护的操作
}

在上述代码中,使用 while 循环来判断等待条件是否满足。如果条件不满足,线程将继续等待,并释放锁,直到其他线程通过 notify()notifyAll() 方法唤醒它。一旦线程被唤醒,它会重新尝试获取锁并检查等待条件是否满足。如果条件仍然不满足,线程会继续等待并重复这个过程,直到条件满足或满足退出条件。 需要注意的是,当线程在等待过程中被中断时,它会抛出 InterruptedException 异常。因此,在等待过程中需要适当地处理中断异常,以确保程序的正确性和可靠性。 通过循环结构和适当的异常处理,你可以确保即使某个线程在竞争锁的过程中失败,它也能够重新尝试获取锁并继续执行。这样可以更好地处理竞争情况,并确保线程的正确执行。

wait/notify、sleep的异同

wait(), notify(), 和 notifyAll() 方法是属于对象的,它们被定义在 java.lang.Object 类中。这些方法用于实现线程间的等待/通知机制,通过对象的监视器(锁)来实现线程的同步和协作。 异同点如下:

  1. 对象关联:wait(), notify(), 和 notifyAll() 方法需要在同步代码块或同步方法中使用,并且操作的是同步对象的监视器。
  2. 线程状态切换:调用 wait() 方法会使当前线程进入等待状态(WAITING 或 TIMED_WAITING),并释放当前持有的对象锁。当其他线程调用相同对象上的 notify()notifyAll() 方法时,等待线程将被唤醒,重新进入就绪状态,以竞争获取对象锁。调用 wait() 方法的线程还可以被中断,会抛出 InterruptedException 异常。
  3. 条件满足:wait() 方法的调用通常会与一个条件相关,只有在条件满足时才会被唤醒,否则会继续等待。这可以通过使用 while 循环来检查条件,并在条件不满足时继续调用 wait() 方法。
  4. 时间限制:wait() 方法可以设置等待超时时间,使用带有超时参数的重载方法 wait(long timeout)。当等待时间达到指定的超时时间后,线程会自动唤醒并重新进入就绪状态。

sleep() 方法是 Thread 类的静态方法,它可以让当前线程进入暂停状态(TIMED_WAITING),暂停一段指定的时间。sleep() 方法是线程级别的操作,不需要对象的监视器,也不会释放对象锁。 异同点如下:

  1. 调用方式:sleep() 方法是通过 Thread.sleep() 进行调用,是一个静态方法,可以直接由线程类调用。
  2. 线程状态切换:调用 sleep() 方法会使当前线程进入休眠状态(TIMED_WAITING),暂停指定的时间,然后自动唤醒并重新进入就绪状态,以便再次竞争执行。
  3. 时间限制:sleep() 方法可以设置暂停的时间,以毫秒为单位,也可以使用带有纳秒参数的重载方法 sleep(long millis, int nanos)
  4. 不依赖对象锁:sleep() 方法不需要对象的监视器(锁),它是线程级别的操作,不会影响其他线程对同一对象的访问。

总结起来,wait(), notify(), 和 notifyAll() 方法是用于线程间的等待/通知机制,操作对象的监视器来实现同步和协作。而 sleep() 方法是用于线程的暂停,让当前线程休眠一段时间。它不需要对象的监视器,也不会释放对象锁。sleep() 方法是线程级别的操作,可以让当前线程暂停执行一段时间后再继续执行。

join期间,线程处于哪种状态

在调用 join() 方法后,当前线程会进入等待状态,直到被调用 join() 的线程执行完毕或超时。在这期间,当前线程的状态会变为 WAITINGTIMED_WAITING 状态,具体取决于是否设置了超时时间。

  • 如果没有设置超时时间,则当前线程的状态会变为 WAITING,表示它正在等待被调用的线程执行完毕。
  • 如果设置了超时时间,则当前线程的状态会变为 TIMED_WAITING,表示它在等待一段指定的时间内,等待被调用的线程执行完毕或超时。

在等待期间,当前线程会让出 CPU 资源,不再参与竞争执行。一旦被调用的线程执行完毕或超时,当前线程会重新进入就绪状态,以便再次竞争执行。 需要注意的是,如果被调用的线程在 join() 期间被中断,当前线程也会响应中断,并抛出 InterruptedException 异常。在使用 join() 方法时,应该适当地处理中断异常,以确保程序的正确性和可靠性。

yield和sleep区别

yield()sleep() 是线程调度相关的方法,它们有一些区别:

  1. 功能不同:
  • yield() 方法是一个静态方法,它的作用是让当前线程主动让出 CPU 资源,使得其他具有相同优先级的线程有机会执行。调用 yield() 方法后,当前线程从运行状态转变为就绪状态,然后再次参与线程调度的竞争。
  • sleep() 方法是一个静态方法,它的作用是让当前线程暂停执行一段时间。调用 sleep() 方法后,当前线程进入阻塞状态,暂停指定的时间,然后重新进入就绪状态,以便再次参与线程调度的竞争。
  1. 参数不同:
  • yield() 方法没有参数,它只是简单地让出 CPU 资源,让其他线程有机会执行。
  • sleep() 方法可以接受一个时间参数,可以指定线程暂停的时间。该时间可以是毫秒级别的整数或纳秒级别的整数。
  1. 影响范围不同:
  • yield() 方法只会影响具有相同优先级的线程,它只是提供了一种暗示给线程调度器,告诉它可以让其他线程先执行。但是并不保证具体的效果,调度器可能会忽略 yield() 的调用。
  • sleep() 方法会暂停当前线程的执行,不管其他线程的优先级如何。

总的来说,yield() 方法是一种协作的方式,让出 CPU 资源给其他线程,而 sleep() 方法是一种主动暂停的方式,指定线程暂停一段时间。它们的具体应用场景取决于程序设计的需要。

守护线程和普通线程区别

守护线程(Daemon Thread)和普通线程(User Thread)在以下几个方面有区别:

  1. 定义:
  • 守护线程:守护线程是一种在后台运行的线程,它的存在并不会阻止程序的终止。当所有的普通线程结束后,守护线程会自动被终止。守护线程通常被用于执行一些支持性的、周期性的或辅助性的任务。
  • 普通线程:普通线程是指用户创建的线程,它们不会自动终止,直到其任务执行完成或显式调用了线程的终止方法。
  1. 生命周期:
  • 守护线程:守护线程的生命周期与程序的生命周期相同。当所有的普通线程结束后,守护线程会被自动终止。
  • 普通线程:普通线程的生命周期由程序员显式控制,直到调用了线程的终止方法或线程执行完毕。
  1. 终止:
  • 守护线程:守护线程会在没有普通线程运行时自动终止。当所有的普通线程都已经结束或主线程结束时,守护线程会被终止,而不管它是否执行完任务。
  • 普通线程:普通线程会一直运行,直到任务执行完毕或调用了线程的终止方法。
  1. 是否影响程序终止:
  • 守护线程:守护线程的终止不会阻止程序的终止。即使守护线程还未执行完毕,程序也可以正常退出。
  • 普通线程:普通线程的终止会阻止程序的终止。如果还有未终止的普通线程存在,程序会等待这些线程执行完毕才能正常退出。

需要注意的是,守护线程不能用来执行一些需要确保执行完整的任务,因为它可能会在任何时候被终止。它适用于执行一些周期性的、支持性的或辅助性的任务,例如垃圾回收器(GC)线程。 在Java中,可以使用 setDaemon(true) 方法将一个线程设置为守护线程,通过 isDaemon() 方法来判断一个线程是否为守护线程。默认情况下,线程都是普通线程,需要显式设置为守护线程。

讲讲java异常体系

Java异常体系是用于处理程序中出现的错误和异常情况的一组类和接口。它基于面向对象的原则,提供了一种结构化的方式来处理和传播异常。

Java异常体系的核心类是 Throwable,它是所有异常类的基类,分为两个子类:ExceptionError

  1. Exception:Exception 是程序中可预见的异常情况的基类。它包括一系列的子类,如 RuntimeExceptionIOExceptionSQLException 等。Exception 分为两种类型:
  • 受检异常(Checked Exception):这些异常在编译时强制要求程序显式处理或声明抛出。例如,IOException、SQLException 等。受检异常的处理包括使用 try-catch 块捕获并处理异常,或者在方法声明中使用 throws 关键字声明该异常。
  • 运行时异常(RuntimeException):这些异常是在运行时由虚拟机抛出的,不需要显式地捕获或声明。例如,NullPointerException、ArrayIndexOutOfBoundsException 等。运行时异常通常是由程序逻辑错误引起的,因此它们通常被认为是可以通过改进代码来避免的。
  1. Error:Error 是指程序无法恢复或无法处理的严重问题,它通常表示虚拟机内部的错误或资源耗尽。例如,OutOfMemoryError、StackOverflowError 等。与异常不同,错误通常是无法捕获和处理的,因为它们发生时表示系统处于不稳定状态。

除了上述核心类,Java 异常体系还包括一些其他重要的类和接口:

  • Throwable:所有异常和错误的基类。
  • RuntimeException:运行时异常的基类,表示程序逻辑错误或违反语义的异常。
  • Exception:可预见的异常的基类,包括受检异常和运行时异常。
  • Error:严重错误的基类,表示虚拟机内部错误或资源耗尽。
  • ExceptionInInitializerError:用于表示静态初始化器中的错误。
  • NullPointerException:当对一个空引用进行操作时抛出的异常。
  • ClassCastException:类型转换异常,当试图将一个对象转换为不兼容的类型时抛出。
  • IOException:输入输出异常,用于处理输入输出相关的错误。
  • RuntimeException 的子类还有一些常见的异常,如 ArithmeticExceptionIllegalArgumentExceptionIndexOutOfBoundsException 等。

通过处理异常,程序可以更好地应对错误和异常情况,并提供适当的错误处理和恢复机制。异常处理使得代码更加健壮,减少了程序中出现致命错误导致程序崩溃的可能性,并提供了更好的调试和错误排查能力。

run方法是否可以抛出异常?如果抛出异常,线程的状态会是怎么样?

在实现 Runnable 接口时,run() 方法的声明中不能添加异常抛出声明。这是因为 run() 方法在 Runnable 接口中的定义不包含任何异常声明。

如果在 run()``方法中抛出了未捕获的异常,那么异常将被传递到线程的顶层,并由线程的 UncaughtExceptionHandler进行处理。如果线程没有设置自定义的UncaughtExceptionHandler`,则异常将导致线程终止,并将异常信息输出到标准错误流。

当线程的 run()方法抛出异常时,线程的状态会变为 TERMINATED(终止状态)。这表示线程执行完毕或因异常而终止。线程的终止状态是最终状态,线程不能再次启动。