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()方法将创建一个新线程,并在该线程中调用MyRunnablerun()方法。而直接调用run()方法则不会创建新线程,而是在当前线程(这里是主线程)中执行run()方法中的代码。

64. 创建线程池有哪几种方式?

在Java中,创建线程池可以通过几种方式实现,通常使用java.util.concurrent.Executors工厂类或者直接实例化java.util.concurrent.ThreadPoolExecutor。以下是几种常见的创建线程池的方式:

  1. 使用Executors
    • 固定大小的线程池:使用Executors.newFixedThreadPool(int nThreads)创建一个线程池,该线程池的线程数量固定,如果某个线程因为异常结束,那么会补充一个新的线程。
    • 单线程的Executor:使用Executors.newSingleThreadExecutor()创建一个单线程的执行器,所有提交的任务都会被串行执行。
    • 可缓存的线程池:使用Executors.newCachedThreadPool()创建一个根据需要创建新线程的线程池,对于旧线程,如果60秒没有被使用就将被回收。
    • 计划任务的线程池:使用Executors.newScheduledThreadPool()创建一个可以定时执行任务的线程池。
  2. 直接使用ThreadPoolExecutor构造函数
    • 可以通过直接实例化ThreadPoolExecutor类来创建自定义的线程池。这需要指定以下参数:
      • int corePoolSize:核心线程数。
      • int maximumPoolSize:最大线程数。
      • long keepAliveTime:非核心线程的空闲存活时间。
      • TimeUnit unit:存活时间的单位。
      • BlockingQueue<Runnable> workQueue:工作队列。
      • ThreadFactory threadFactory:可选,用于创建新线程的工厂。
      • RejectedExecutionHandler handler:可选,当任务无法执行时的策略。
  3. 使用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 公众号:码路向前 。

04-19 17:51