63. 线程的 run() 和 start() 有什么区别?
在Java中,run()方法和start()方法是线程操作中的两个核心方法,它们来自于Thread类。
run()方法:
- run()方法是一个线程的实际执行代码所在的方法。
- 它是一个由Runnable接口定义的抽象方法,因此任何实现Runnable接口的类都需要覆盖这个方法。
- 当run()方法被调用时,它会在当前线程的上下文中执行(即,如果直接调用一个线程对象的run()方法,它会在调用它的线程中运行,而不是在新线程中运行)。
- run()方法可以多次调用,它仅仅是一个普通的方法调用。
start()方法:
- start()方法是用来启动新线程并执行- run()方法中的代码的。
- 当调用start()方法时,它会创建一个新的线程,然后在这个新线程中调用run()方法。
- 一个线程对象只能调用一次start()方法,多次调用会抛出IllegalThreadStateException。
- start()方法是由JVM调用来启动线程的,而不是由开发者在代码中直接调用的。
以下是两者的简单对比表格:
| 对比项 | run()方法 | start()方法 | 
|---|---|---|
| 调用目的 | 执行线程任务 | 启动一个新线程 | 
| 调用次数 | 可以多次调用 | 只能调用一次 | 
| 调用效果 | 在当前线程中执行 | 创建新线程,在新线程中执行 run() | 
| 异常处理 | 直接在调用线程中抛出 | 任何未捕获异常由新线程处理 | 
| 方法来源 | 实现 Runnable接口时覆盖 | 继承 Thread类时使用 | 
以下是一个简单的代码示例说明这两个方法的不同:
class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("Thread is running: " + Thread.currentThread().getName());}
}public class ThreadExample {public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();// 通过start()方法启动新线程Thread thread = new Thread(myRunnable);thread.start(); // 正确使用start()方法启动线程// 直接调用run()方法,不会启动新线程myRunnable.run(); // 这将在main线程中执行,而不是新线程// 以下调用是错误的,会抛出异常// thread.start(); // 不能重复调用start()方法}
}
在这个示例中,我们创建了一个实现Runnable接口的类MyRunnable,并在主方法中创建了Thread对象。调用start()方法将创建一个新线程,并在该线程中调用MyRunnable的run()方法。而直接调用run()方法则不会创建新线程,而是在当前线程(这里是主线程)中执行run()方法中的代码。
64. 创建线程池有哪几种方式?
在Java中,创建线程池可以通过几种方式实现,通常使用java.util.concurrent.Executors工厂类或者直接实例化java.util.concurrent.ThreadPoolExecutor。以下是几种常见的创建线程池的方式:
- 使用Executors类:- 固定大小的线程池:使用Executors.newFixedThreadPool(int nThreads)创建一个线程池,该线程池的线程数量固定,如果某个线程因为异常结束,那么会补充一个新的线程。
- 单线程的Executor:使用Executors.newSingleThreadExecutor()创建一个单线程的执行器,所有提交的任务都会被串行执行。
- 可缓存的线程池:使用Executors.newCachedThreadPool()创建一个根据需要创建新线程的线程池,对于旧线程,如果60秒没有被使用就将被回收。
- 计划任务的线程池:使用Executors.newScheduledThreadPool()创建一个可以定时执行任务的线程池。
 
- 固定大小的线程池:使用
- 直接使用ThreadPoolExecutor构造函数:- 可以通过直接实例化ThreadPoolExecutor类来创建自定义的线程池。这需要指定以下参数:- int corePoolSize:核心线程数。
- int maximumPoolSize:最大线程数。
- long keepAliveTime:非核心线程的空闲存活时间。
- TimeUnit unit:存活时间的单位。
- BlockingQueue<Runnable> workQueue:工作队列。
- ThreadFactory threadFactory:可选,用于创建新线程的工厂。
- RejectedExecutionHandler handler:可选,当任务无法执行时的策略。
 
 
- 可以通过直接实例化
- 使用ForkJoinPool:- 对于需要大量计算并且能够进行分治任务拆分的应用,可以使用ForkJoinPool。它适用于递归算法或者可以递归分解为更小任务的大任务。
 
- 对于需要大量计算并且能够进行分治任务拆分的应用,可以使用
以下是使用ThreadPoolExecutor创建线程池的示例代码:
import java.util.concurrent.*;public class CustomThreadPool {public static void main(String[] args) {// 创建自定义线程池ThreadPoolExecutor executor = new ThreadPoolExecutor(2, // 核心线程数4, // 最大线程数60, // 非核心线程空闲存活时间TimeUnit.SECONDS, // 存活时间单位new ArrayBlockingQueue<>(10) // 工作队列);// 提交任务for (int i = 0; i < 10; i++) {executor.submit(() -> {System.out.println("执行任务: " + Thread.currentThread().getName());});}// 关闭线程池executor.shutdown();}
}
以上就是创建线程池的几种方式及其简单示例。注意,在实际的生产环境中,建议直接使用ThreadPoolExecutor构造函数来精确控制线程池的行为,避免因为默认参数而引发的潜在问题。
有帮助请点赞收藏呀~
领【150 道精选 Java 高频面试题】请 go 公众号:码路向前 。