Java并发编程 | 创建线程的几种方式

文章目录

    • 一、简介
    • 二、使用Thread类创建线程
      • 2.1 继承Thread类
        • 2.1.1 创建Thread子类
        • 2.1.2 重写run方法
        • 2.1.3 启动线程的方式
          • 方式一:创建线程对象后调用start方法
          • 方式二:直接使用匿名内部类创建线程对象并调用start方法
      • 2.2 使用匿名内部类创建线程
      • 2.3 线程的生命周期和状态转换
    • 三、使用Runnable接口创建线程
      • 3.1 实现Runnable接口
        • 3.1.1 创建Runnable实现类
        • 3.1.2 实现run方法
      • 3.2 将Runnable实例传递给Thread对象
      • 3.3 与Thread类创建线程的比较
    • 四、使用Executor框架创建线程
      • 4.1 Executor框架概述
      • 4.2 使用ExecutorService接口
        • 4.2.1 创建ExecutorService实例
        • 4.2.2 提交任务和获取Future对象
        • 4.2.3 关闭ExecutorService
      • 4.3 线程池的优势和配置
    • 五、使用Callable和Future创建线程
      • 5.1 Callable接口概述
      • 5.2 创建Callable实现类
        • 5.2.1 实现call方法
      • 5.3 使用Future获取任务结果
        • 5.3.1 提交Callable任务
        • 5.3.2 获取Future对象
        • 5.3.3 处理任务结果和异常
    • 六、总结
      • 6.1 不同方式创建线程的比较
      • 6.2 选择合适的线程创建方式
      • 6.3 注意事项和常见问题




一、简介

  在并发编程中,线程是执行代码的基本单位。通过创建多个线程,可以实现并发执行,提高程序的效率和响应性。

  多线程编程具有以下优势:

  • 提高程序的运行效率和响应性。
  • 充分利用多核处理器的计算能力。
  • 实现并发任务的协作和通信。




二、使用Thread类创建线程


2.1 继承Thread类

2.1.1 创建Thread子类

  通过继承Thread类,可以创建自定义的线程类。

public class MyThread extends Thread {
    // 构造方法可以用于传递线程名称等参数
    public MyThread(String name) {
        super(name);
    }

    // 重写run方法,定义线程执行的代码逻辑
    @Override
    public void run() {
        // 线程执行的代码逻辑
        System.out.println("线程执行中:" + getName());
    }
}

2.1.2 重写run方法

  在创建Thread子类时,需要重写run方法,定义线程执行的代码逻辑。

public class MyThread extends Thread {
    @Override
    public void run() {
        // 线程执行的代码逻辑
        System.out.println("线程执行中:" + getName());
    }
}

2.1.3 启动线程的方式

  创建并启动线程的方式有两种:

方式一:创建线程对象后调用start方法
MyThread thread = new MyThread("线程1");
thread.start();
方式二:直接使用匿名内部类创建线程对象并调用start方法
Thread thread = new Thread("线程2") {
    @Override
    public void run() {
        // 线程执行的代码逻辑
        System.out.println("线程执行中:" + getName());
    }
};

thread.start();

2.2 使用匿名内部类创建线程

  使用匿名内部类可以简化线程的创建过程。

Thread thread = new Thread() {
    @Override
    public void run() {
        // 线程执行的代码逻辑
        System.out.println("线程执行中:" + getName());
    }
};

thread.start();

2.3 线程的生命周期和状态转换

  线程的生命周期包括新建、就绪、运行、阻塞和终止等状态。线程的状态可以通过调用Thread类的方法进行转换。

Thread thread = new Thread() {
    @Override
    public void run() {
        // 线程执行的代码逻辑
        System.out.println("线程执行中:" + getName());
    }
};

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

// 阻塞线程
try {
    Thread.sleep(1000);
} catch (InterruptedException e) {
    e.printStackTrace();
}

// 终止线程
thread.interrupt();




三、使用Runnable接口创建线程


3.1 实现Runnable接口

3.1.1 创建Runnable实现类

  通过实现Runnable接口,可以创建自定义的线程类。

public class MyRunnable implements Runnable {
    // 实现run方法,定义线程执行的代码逻辑
    @Override
    public void run() {
        // 线程执行的代码逻辑
        System.out.println("线程执行中:" + Thread.currentThread().getName());
    }
}

3.1.2 实现run方法

  在创建Runnable实现类时,需要实现run方法,定义线程执行的代码逻辑。

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 线程执行的代码逻辑
        System.out.println("线程执行中:" + Thread.currentThread().getName());
    }
}

3.2 将Runnable实例传递给Thread对象

  使用Runnable接口创建线程时,需要将实现了Runnable接口的实例传递给Thread对象。

MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);

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

3.3 与Thread类创建线程的比较

  使用Runnable接口创建线程相比于继承Thread类创建线程,具有以下优势:

  • 避免单继承的限制:Java中,一个类只能继承一个父类,但可以实现多个接口。通过实现Runnable接口,可以灵活地在不同的类中复用线程代码。

  • 提高代码的可扩展性:将线程的执行逻辑与线程类解耦,可以更方便地对线程的执行逻辑进行修改和扩展。

  • 适合资源共享:多个线程可以共享同一个Runnable实例,从而实现对共享资源的访问和操作。

  使用Runnable接口创建线程是一种更加灵活和可扩展的方式,适用于多线程环境中的资源共享和代码复用。



四、使用Executor框架创建线程


4.1 Executor框架概述

  Executor框架是Java提供的用于管理和执行线程的高级工具。它通过将任务的提交和执行进行解耦,提供了更灵活、可控的线程管理方式。

4.2 使用ExecutorService接口

4.2.1 创建ExecutorService实例

  使用Executor框架创建线程的核心接口是ExecutorService。可以通过Executors类提供的静态方法来创建ExecutorService实例。

ExecutorService executorService = Executors.newFixedThreadPool(5);

  上述代码创建了一个固定大小为5的线程池。

4.2.2 提交任务和获取Future对象

  通过ExecutorService的submit方法可以提交任务,并返回一个表示任务结果的Future对象。

Future<String> future = executorService.submit(new Callable<String>() {
    @Override
    public String call() throws Exception {
        // 任务执行的代码逻辑
        return "任务执行结果";
    }
});

// 获取任务执行结果
try {
    String result = future.get();
    System.out.println("任务执行结果:" + result);
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

4.2.3 关闭ExecutorService

  在使用完ExecutorService后,应该及时关闭以释放资源。

executorService.shutdown();

  上述代码关闭了ExecutorService,不再接受新的任务提交,但会等待已提交的任务执行完成。

4.3 线程池的优势和配置

  使用线程池的优势包括:

  • 重用线程:线程池中的线程可以被重复利用,避免了线程的频繁创建和销毁,提高了线程的利用率。

  • 控制并发数:线程池可以限制并发执行的线程数,避免因线程过多导致系统资源耗尽的问题。

  • 提供任务队列:线程池可以提供一个任务队列,将未执行的任务暂存起来,等待线程空闲时执行。


  线程池的配置可以根据实际需求进行调整,常用的配置参数包括:

  • 核心线程数:线程池中始终保持的线程数量。

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

  • 任务队列:用于存放未执行的任务的队列。

  • 线程空闲时间:当线程空闲时间超过设定值时,多余的线程会被销毁。

  • 拒绝策略:当线程池无法接受新的任务时,如何处理新的任务。

  可以通过调用Executors类提供的方法来创建不同类型的线程池,并根据实际需求进行配置。




五、使用Callable和Future创建线程


5.1 Callable接口概述

  Callable接口是Java提供的一个用于表示可返回结果的任务的接口。与Runnable接口不同,Callable接口的call方法可以返回执行结果,并且可以抛出异常。

5.2 创建Callable实现类

5.2.1 实现call方法

  创建一个实现了Callable接口的类,并实现其call方法,定义任务的执行逻辑。

import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        // 任务执行的代码逻辑
        return "任务执行结果";
    }
}

5.3 使用Future获取任务结果

5.3.1 提交Callable任务

  将实现了Callable接口的实例提交给ExecutorService来执行。

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

public class Main {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        Future<String> future = executorService.submit(new MyCallable());
    }
}

5.3.2 获取Future对象

  通过submit方法返回的Future对象,可以获取任务的执行结果。

try {
    String result = future.get();
    System.out.println("任务执行结果:" + result);
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}

5.3.3 处理任务结果和异常

  在获取任务结果时,可能会抛出InterruptedException和ExecutionException异常,需要进行相应的异常处理。

try {
    String result = future.get();
    System.out.println("任务执行结果:" + result);
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (ExecutionException e) {
    e.printStackTrace();
}

  以上是使用Callable和Future创建线程的基本方法和处理任务结果和异常的示例。通过Callable接口和Future对象,可以实现具有返回结果的任务,并对任务的执行结果进行处理。




六、总结


6.1 不同方式创建线程的比较

  在Java中,有多种方式可以创建线程,包括继承Thread类、实现Runnable接口以及使用Callable和Future。这些方式各有特点,可以根据具体的需求选择合适的方式。

  • 继承Thread类:简单易用,但线程与任务耦合度较高,无法复用线程对象。
  • 实现Runnable接口:解耦了线程和任务,可以实现线程的复用,适合多个线程共享一个任务的场景。
  • 使用Callable和Future:可以获取任务的执行结果,并处理可能的异常,适合需要获取任务返回结果的场景。

6.2 选择合适的线程创建方式

  选择合适的线程创建方式取决于具体的需求和场景:

  • 如果只是简单地执行一个任务,且不需要获取任务的返回结果,可以考虑继承Thread类或实现Runnable接口。
  • 如果需要获取任务的返回结果,可以使用Callable和Future,通过Future对象获取任务的执行结果。
  • 如果需要多个线程共享一个任务,可以实现Runnable接口,并将任务对象传递给多个线程进行执行。

6.3 注意事项和常见问题

  在使用多线程时,需要注意以下事项和常见问题:

  • 线程安全:多个线程同时操作共享数据时可能出现线程安全问题,需要采取同步措施来保证数据的一致性。
  • 异常处理:在使用Callable和Future时,需要处理可能抛出的异常,以防止程序出现异常后无法正常执行。
  • 线程池的使用:使用线程池可以提高线程的复用性和管理性,避免频繁创建和销毁线程的开销。
  • 避免死锁:在多线程编程中,需要注意避免死锁情况的发生,合理设计锁的获取和释放顺序。

  合理选择线程创建方式,并注意处理线程安全和异常问题,可以更好地进行多线程编程。

你可能感兴趣的:(JAVA,java,jvm,开发语言)