上一篇地址:赶紧收藏!2024 年最常见 100道 Java 基础面试题(十九)-CSDN博客
三十九、Runnable
和Callable
有什么区别?
Runnable
和Callable
都是Java中用于创建线程的任务接口,但它们之间存在一些关键的区别:
-
返回类型:
Runnable
接口的run()
方法没有返回类型,它通常用于执行某些操作而不返回结果。Callable
接口的call()
方法返回一个泛型类型,可以返回执行结果。
-
异常:
Runnable
的run()
方法不声明抛出任何异常,这意味着它不能抛出未检查异常(unchecked exceptions)。Callable
的call()
方法可以抛出异常,包括未检查异常和检查异常(checked exceptions),这使得Callable
在处理异常时更加灵活。
-
Future:
Callable
任务经常与Future
或FutureTask
结合使用,允许开发者获取任务的执行结果,或者在任务完成前取消任务。Runnable
任务不与Future
结合,因此无法获得任务的执行结果或取消任务。
-
线程池:
- 在使用线程池(如
ExecutorService
)时,Callable
任务可以通过Future
对象来查询任务状态、取消任务或获取任务返回值。 Runnable
任务在线程池中通常通过submit()
方法提交,并且可以返回一个Future
对象,但任务本身不直接返回结果。
- 在使用线程池(如
-
内部类:
- 如果任务是一个类内部的逻辑,并且该逻辑不需要返回结果,那么实现
Runnable
接口可能更方便。 - 如果任务需要返回结果,或者需要抛出异常,那么实现
Callable
接口可能更合适。
- 如果任务是一个类内部的逻辑,并且该逻辑不需要返回结果,那么实现
-
示例代码:
-
// Runnable示例 class MyRunnable implements Runnable { public void run() { // 执行任务,无返回值 } } // Callable示例 class MyCallable implements Callable<String> { public String call() { // 执行任务并返回结果 return "任务结果"; } } // 使用ExecutorService提交任务 ExecutorService executor = Executors.newFixedThreadPool(3); Future<String> future = executor.submit(new MyCallable()); String result = future.get(); // 等待任务完成并获取结果 executor.shutdown();
总结:
Runnable
适用于不需要返回结果的线程任务。Callable
适用于需要返回结果或处理异常的线程任务。Callable
任务与Future
结合使用,可以提供任务执行的更多控制,如查询状态、取消任务或获取结果。
四十、线程有哪些状态?
在Java中,线程(Thread)在其生命周期中会经历不同的状态。根据Java虚拟机(JVM)的规范,线程的状态可以分为以下几个:
-
新建(New): 线程对象创建后,但还未调用
start()
方法之前,线程处于新建状态。 -
可运行(Runnable): 线程对象调用了
start()
方法后,线程进入可运行状态。在可运行状态的线程可能正在执行,也可能正在等待CPU资源来执行。 -
阻塞(Blocked): 当线程等待某个资源(如等待进入同步代码块或等待I/O操作完成)时,它将从可运行状态进入阻塞状态。
-
无限期等待(Waiting): 线程因为等待其他线程执行特定操作(如等待某个条件变量或等待
join()
操作)而进入无限期等待状态。线程在这个状态下需要其他线程的交互才能唤醒。 -
限期等待(Timed Waiting): 线程在执行操作时指定了超时时间,如调用了
sleep(long millis)
、wait(long timeout)
、join(long millis)
或LockSupport.parkNanos()
等方法,线程将进入限期等待状态。超时时间到达后,线程会自动唤醒,回到可运行状态。 -
终止(Terminated): 线程执行完
run()
方法中的所有操作,或者因为异常退出了run()
方法,此时线程进入终止状态。
状态转换:
- 新建 -> 可运行:调用线程对象的
start()
方法。 - 可运行 -> 阻塞:线程试图获取一个同步锁或执行了阻塞I/O操作。
- 可运行 -> 无限期等待:线程执行了
wait()
、join()
或LockSupport.park()
方法。 - 可运行 -> 限期等待:线程执行了
sleep(long millis)
、join(long millis)
、LockSupport.parkNanos()
或LockSupport.parkUntil()
方法。 - 限期等待 -> 可运行:超时时间到达或被
interrupt()
方法中断。 - 无限期等待 -> 可运行:线程被其他线程唤醒或被
interrupt()
方法中断。
示例代码:
public class ThreadStateExample {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
synchronized (ThreadStateExample.class) {
while (!Thread.currentThread().isInterrupted()) {
try {
ThreadStateExample.class.wait(); // 无限期等待
} catch (InterruptedException e) {
break; // 被中断,退出循环
}
}
}
});
thread.start(); // 新建 -> 可运行
synchronized (ThreadStateExample.class) {
ThreadStateExample.class.notify(); // 唤醒等待的线程
}
try {
Thread.sleep(1000); // 主线程限期等待
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt(); // 中断线程,使其从等待状态唤醒
}
}
总结:
- 线程在生命周期中会经历不同的状态,从新建到终止。
- 线程状态的变化是由线程执行的操作和JVM的线程调度器共同决定的。
- 了解线程状态及其转换对于编写多线程程序和进行性能调优非常重要。