63. 线程的 run() 和 start() 有什么区别?
在Java中,run()
方法和start()
方法是线程操作中的两个核心方法,它们来自于Thread
类。
run()
方法:
run()
方法是一个线程的实际执行代码所在的方法。- 它是一个由
Runnable
接口定义的抽象方法,因此任何实现Runnable
接口的类都需要覆盖这个方法。 - 当
run()
方法被调用时,它会在当前线程的上下文中执行(即,如果直接调用一个线程对象的run()
方法,它会在调用它的线程中运行,而不是在新线程中运行)。 run()
方法可以多次调用,它仅仅是一个普通的方法调用。
start()
方法:
start()
方法是用来启动新线程并执行run()
方法中的代码的。- 当调用
start()
方法时,它会创建一个新的线程,然后在这个新线程中调用run()
方法。 - 一个线程对象只能调用一次
start()
方法,多次调用会抛出IllegalThreadStateException
。 start()
方法是由JVM调用来启动线程的,而不是由开发者在代码中直接调用的。
以下是两者的简单对比表格:
以下是一个简单的代码示例说明这两个方法的不同:
class MyRunnable implements Runnable {
@Override
public 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 公众号:码路向前 。