线程创建

创建线程有以下四种方式:

  • 继承 Threa 类

  • 实现 Runable 接口

  • 实现 Callable 接口

  • 线程池

继承 Thread 类

  1. 创建自定义线程类

首先,你需要创建一个新的类,这个类继承自 java.lang.Thread 类。在这个类中,你通常需要重写 run() 方法。run() 方法是线程的主体,当线程启动后,run() 方法中的代码将被执行。

  1. 创建线程对象

接下来,你需要创建 MyThread 类的实例,也就是创建线程对象。

  1. 启动线程

最后,你需要调用线程对象的 start() 方法来启动线程。start() 方法会启动一个新的线程,并自动调用 run() 方法。注意,不要直接调用 run() 方法,因为那样只会在当前线程中执行 run() 方法中的代码,而不会创建一个新的线程。

示例代码

下面是一个完整的示例代码,演示了如何通过继承 Thread 类来创建线程:

public class Main {  
    public static void main(String[] args) {  
        // 创建线程对象  
        MyThread myThread = new MyThread();  

        // 启动线程  
        myThread.start();  

        // 主线程继续执行其他任务  
        System.out.println("主线程继续执行...");  
    }  
}  

class MyThread extends Thread {  
    @Override  
    public void run() {  
        // 线程执行的代码放在这里  
        System.out.println("线程运行中...");  
    }  
}

在上面的示例中,MyThread 类继承了 Thread 类,并重写了 run() 方法。在 main 方法中,我们创建了 MyThread 类的实例 myThread,并调用 start() 方法启动线程。启动线程后,myThreadrun() 方法将在新的线程中执行,而主线程将继续执行 main 方法中的其他代码。

注意事项

  • 继承 Thread 类的方式虽然简单直观,但有一些缺点。比如,Java 不支持多重继承,所以如果你的类已经继承了其他类,就不能再继承 Thread 类。在这种情况下,你应该使用实现 Runnable 接口的方式来创建线程。

  • 在实际开发中,通常推荐使用实现 Runnable 接口的方式来创建线程,因为这种方式更加灵活,而且更符合面向对象的设计原则。

实现Runable接口

在 Java 中,实现 Runnable 接口是创建线程的一种常用方式,尤其当你不希望继承自 Thread 类(可能是因为已经继承了其他类或者想要实现更灵活的线程管理)时,这种方式非常有用。下面我将详细解释如何使用 Runnable 接口来创建线程。

  1. 创建实现 Runnable 接口的类

首先,你需要创建一个新的类,并实现 java.lang.Runnable 接口。Runnable 接口只有一个方法需要实现,那就是 run() 方法。在这个方法中,你定义线程需要执行的任务。

  1. 创建 Thread 对象并传入 Runnable 实例

接下来,你需要创建 Thread 类的实例,并将实现了 Runnable 接口的对象作为参数传递给 Thread 类的构造函数。

  1. 启动线程

最后,通过调用 Thread 对象的 start() 方法来启动线程。这将导致一个新的线程被创建,并自动调用 Runnable 实例的 run() 方法。

示例代码

下面是一个完整的示例代码,演示了如何通过实现 Runnable 接口来创建线程:

public class Main {  
    public static void main(String[] args) {  
        // 创建实现 Runnable 接口的实例  
        MyRunnable myRunnable = new MyRunnable();  

        // 创建 Thread 对象,并传入 Runnable 实例  
        Thread thread = new Thread(myRunnable);  

        // 启动线程  
        thread.start();  

        // 主线程继续执行其他任务  
        System.out.println("主线程继续执行...");  
    }  
}  

class MyRunnable implements Runnable {  
    @Override  
    public void run() {  
        // 线程执行的代码放在这里  
        System.out.println("线程运行中...");  
    }  
}

在上面的示例中,MyRunnable 类实现了 Runnable 接口,并重写了 run() 方法。在 main 方法中,我们创建了 MyRunnable 类的实例 myRunnable,并使用这个实例创建了一个 Thread 对象 thread。接着,我们调用 threadstart() 方法来启动线程。启动线程后,myRunnablerun() 方法将在新的线程中执行,而主线程将继续执行 main 方法中的其他代码。

注意事项

  • 实现 Runnable 接口的方式比继承 Thread 类更灵活,因为 Java 只支持单继承,但可以实现多个接口。如果你的类已经继承了其他类,那么实现 Runnable 接口是创建线程的唯一选择。

  • 使用 Runnable 接口还可以方便地实现线程池和线程共享资源,因为可以创建多个实现了 Runnable 接口的对象,并将它们提交给线程池执行。

  • 在实际开发中,通常推荐使用实现 Runnable 接口的方式来创建线程,特别是当需要编写复杂的线程逻辑或者需要多个线程共享数据时。

实现Callbale接口

在 Java 中,Callable 接口是 java.util.concurrent 包的一部分,它用于创建可以返回结果或抛出异常的线程。与 Runnable 接口不同,Callable 接口定义了一个 call() 方法,该方法可以返回一个值,并可能声明抛出异常。要执行 Callable 任务,你需要使用 FutureTask 类或者 ExecutorService 接口的实现类(如 ThreadPoolExecutor)。

  1. 创建实现 Callable 接口的类

首先,你需要创建一个新的类,并实现 java.util.concurrent.Callable 接口。Callable 接口只有一个需要实现的方法,即 call()。在这个方法中,你可以定义线程需要执行的任务,并返回一个结果。

  1. 使用 ExecutorService 提交 Callable 任务

接下来,你需要创建一个 ExecutorService 的实例(比如 ThreadPoolExecutorExecutors 工具类创建的线程池),并使用 submit() 方法提交 Callable 任务。submit() 方法会返回一个 Future 对象,它代表异步计算的结果。

示例代码

import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
import java.util.concurrent.Future;  

public class Main {  
    public static void main(String[] args) throws Exception {  
        // 创建一个固定大小的线程池  
        ExecutorService executorService = Executors.newFixedThreadPool(1);  

        // 提交 Callable 任务  
        Future<String> future = executorService.submit(new MyCallable());  

        // 获取 Callable 任务的结果  
        String result = future.get(); // 这会阻塞,直到任务完成  

        // 打印结果  
        System.out.println("任务结果: " + result);  

        // 关闭线程池  
        executorService.shutdown();  
    }  
}

使用 Callable 接口创建线程的主要步骤是:

  • 实现 Callable 接口,并重写 call() 方法。

  • 创建一个 ExecutorService 实例。

  • 使用 submit() 方法提交 Callable 任务,并获得 Future 对象。

  • 调用 Future.get() 方法获取任务的结果(如果需要的话)。

  • 关闭 ExecutorService(不再需要时)。

注意事项

  • Callable 接口允许你返回一个值,这是 Runnable 接口所不具备的。

  • Callable 接口的 call() 方法可以声明抛出异常,这使得错误处理更加灵活。

  • 使用 ExecutorServiceFuture 可以方便地管理线程,包括提交任务、获取结果和关闭线程池。

  • 当你不再需要 ExecutorService 时,应该调用其 shutdown() 方法来优雅地关闭它,释放资源。

在实际应用中,CallableFuture 通常用于需要返回结果的异步任务,比如网络请求、长时间计算等场景。它们提供了比 Runnable 更高级的功能,使得多线程编程更加灵活和强大。

线程池

在 Java 中,创建线程池的主要方式是使用 java.util.concurrent 包中提供的 ExecutorService 接口及其实现类。线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的 ThreadFactory 创建一个新线程。

以下是创建线程池的几种常见方式:

1. 使用 Executors 工厂类

Executors 类提供了几种静态工厂方法,用于创建不同类型的线程池。

1.1 固定大小的线程池

ExecutorService executor = Executors.newFixedThreadPool(int nThreads);

这个线程池会创建固定数量的线程,并且会保持这些线程处于活动状态,即使这些线程是空闲的。如果所有线程都在工作,新提交的任务会被保存在一个队列中,直到有线程可用。

1.2 单线程的线程池

ExecutorService executor = Executors.newSingleThreadExecutor();

这个线程池只有一个线程,这意味着任务会按照提交的顺序一个接一个地执行。

1.3 可缓存的线程池

ExecutorService executor = Executors.newCachedThreadPool();

这个线程池会根据需要创建新线程,但在可用时将尝试重用先前构造的线程。如果线程在 60 秒内未使用,则会被终止并从缓存中移除。

1.4 定时或周期性执行任务的线程池

ScheduledExecutorService executor = Executors.newScheduledThreadPool(int corePoolSize);

这个线程池支持定时和周期性执行任务。

2. 使用 ThreadPoolExecutor 类直接创建

ThreadPoolExecutor 类提供了更丰富的配置选项,允许你更细致地控制线程池的行为。

ThreadPoolExecutor executor = new ThreadPoolExecutor(
    int corePoolSize, 
    int maximumPoolSize, 
    long keepAliveTime, 
    TimeUnit unit, 
    BlockingQueue<Runnable> workQueue, 
    ThreadFactory threadFactory, 
    RejectedExecutionHandler handler
);

参数说明:

  • corePoolSize:线程池中的核心线程数,即使在线程空闲时也不会被销毁,除非设置了 allowCoreThreadTimeOut

  • maximumPoolSize:线程池允许的最大线程数。

  • keepAliveTime:当线程数大于核心线程数时,这是多余的空闲线程在终止前等待新任务的最长时间。

  • unitkeepAliveTime 参数的时间单位。

  • workQueue:用于保存等待执行的任务的阻塞队列。

  • threadFactory:用于创建新线程的线程工厂。

  • handler:当线程池无法处理新任务时使用的饱和策略。

示例代码

使用 ThreadPoolExecutor 创建一个线程池:

import java.util.concurrent.*;

public class ThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个固定大小的线程池
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
            5, // 核心线程数
            10, // 最大线程数
            60L, // 空闲线程等待新任务的最长时间
            TimeUnit.SECONDS, // 时间单位
            new LinkedBlockingQueue<Runnable>(100), // 任务队列
            Executors.defaultThreadFactory(), // 线程工厂
            new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
        );

        // 提交任务到线程池
        for (int i = 0; i < 15; i++) {
            executor.execute(new MyRunnableTask("Task " + i));
        }

        // 关闭线程池
        executor.shutdown();
        while (!executor.isTerminated()) {
        }
        System.out.println("所有任务已完成");
    }

    static class MyRunnableTask implements Runnable {
        private final String taskName;

        MyRunnableTask(String taskName) {
            this.taskName = taskName;
        }

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + " 正在执行 " + taskName);
        }
    }
}

注意事项

  • 合理地设置线程池的大小非常重要,以避免资源不足或资源浪费。

  • 当不再需要线程池时,应调用 shutdown()shutdownNow() 方法来关闭线程池,并等待所有任务完成。

  • 如果线程池中的任务执行时间很长,或者任务数量很大,需要考虑使用有界队列,以避免内存溢出