如何在程序中创建出多条线程

2024-08-26 03:12
文章标签 线程 程序 创建 多条

本文主要是介绍如何在程序中创建出多条线程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

多线程是编程中的一个重要概念,它允许程序同时执行多个任务,每个任务可以看作是一个线程。在Java中,多线程尤为常见且强大,它通过允许程序在并发环境下运行,提高了程序的执行效率和响应速度。以下是对Java多线程的详细讲解:

基本概念

  1. 线程(Thread):线程是进程中的实体,是CPU调度和分派的基本单位,它是比进程更小的独立运行的单位。线程一般不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器、一组寄存器和栈),但它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
  2. 进程(Process):进程是程序的一次动态执行过程,是程序代码、数据和相关资源的集合。一个进程可以拥有多个线程,这些线程共享进程的地址空间和系统资源。

Thread 类

在Java中,Thread 类是处理多线程的核心类。每个线程都是通过 Thread 类的一个实例来表示的。Java 允许你继承 Thread 类来创建新的线程类,或者实现 Runnable 接口。下面我将详细解释 Thread 类中的几个关键方法。

Thread类的常用方法

  1. public void run()

    线程的任务方法。当线程启动时,会自动调用此方法。在继承Thread类创建线程时,通常需要重写此方法以定义线程的具体任务。
  2. public void start()

    启动线程。调用此方法后,线程会进入就绪状态,等待CPU调度执行。注意,直接调用run()方法并不会启动新线程,而是像普通方法一样在当前线程中执行。
  3. public String getName()

    获取当前线程的名称。默认情况下,线程的名称是"Thread-索引",其中索引是一个递增的整数。
  4. public void setName(String name)

    为线程设置名称。通过此方法可以自定义线程的名称,便于在调试和日志记录中识别不同的线程。
  5. public static Thread currentThread()

    获取当前执行的线程对象。此方法允许在代码中获取当前正在执行的线程实例,进而可以调用该线程的方法或属性。
  6. public static void sleep(long time)

    让当前执行的线程休眠指定的毫秒数后,再继续执行。这是一个静态方法,用于暂停当前线程的执行,让出CPU资源给其他线程。
  7. public final void join()

    让调用当前这个方法的线程先执行完。这个方法的作用是等待调用它的线程(即当前线程)终止。在join()方法返回之前,其他线程(即调用join()方法的线程)无法继续执行。

Thread类的常见构造器

  1. public Thread(String name)

    可以为当前线程指定名称。通过构造器中的name参数,可以为线程设置一个易于识别的名称。
  2. public Thread(Runnable target)

    封装Runnable对象成为线程对象。这种方式是实现多线程的另一种途径,即实现Runnable接口。通过这种方式,可以将线程的任务与线程本身分离,使得代码更加灵活。
  3. public Thread(Runnable target, String name)

    封装Runnable对象成为线程对象,并指定线程名称。这个构造器结合了上述两种构造器的功能,既可以将线程的任务与线程本身分离,又可以自定义线程的名称。

创建方式一

通过继承Thread类来创建线程

步骤 1: 定义子类继承Thread

首先,你需要定义一个子类来继承Java的java.lang.Thread类。在这个子类中,你需要重写run方法。run方法是线程启动时执行的代码块。

步骤 2: 创建MyThread类的对象

接着,你需要创建MyThread类的一个或多个对象。这些对象就是线程实例。

步骤 3: 调用线程对象的start方法启动线程

最后,你需要调用线程对象的start方法来启动线程。调用start方法会启动一个新的线程,并让这个线程执行其run方法中的代码。注意,不要直接调用run方法,这样会导致代码在调用它的线程(通常是主线程)中顺序执行,而不会创建新的线程。

为了举一个更明显的例子来说明通过继承Thread类来创建线程,我们可以考虑一个简单的场景:有两个线程,一个负责打印奇数,另一个负责打印偶数。我们将分别创建两个类(OddThreadEvenThread)来继承Thread类,并在它们的run方法中实现打印奇数或偶数的逻辑。

// OddThread 类,继承自 Thread 类,用于打印奇数  
class OddThread extends Thread {  private int start;  private int end;  public OddThread(int start, int end) {  this.start = start;  this.end = end;  }  @Override  public void run() {  for (int i = start; i <= end; i += 2) {  System.out.println(Thread.currentThread().getName() + " 打印奇数: " + i);  try {  // 为了更明显地看到线程切换,可以添加一些延迟  Thread.sleep(100);  } catch (InterruptedException e) {  e.printStackTrace();  }  }  }  
}  // EvenThread 类,继承自 Thread 类,用于打印偶数  
class EvenThread extends Thread {  private int start;  private int end;  public EvenThread(int start, int end) {  this.start = start;  if (end % 2 == 0) {  this.end = end;  } else {  this.end = end - 1; // 确保结束值是偶数  }  }  @Override  public void run() {  for (int i = start; i <= end; i += 2) {  System.out.println(Thread.currentThread().getName() + " 打印偶数: " + i);  try {  // 为了更明显地看到线程切换,可以添加一些延迟  Thread.sleep(100);  } catch (InterruptedException e) {  e.printStackTrace();  }  }  }  
}  // 主类  
public class ThreadExample {  public static void main(String[] args) {  // 创建并启动打印奇数的线程  OddThread oddThread = new OddThread(1, 10);  oddThread.start();  // 创建并启动打印偶数的线程  EvenThread evenThread = new EvenThread(2, 10);  evenThread.start();  // 注意:主线程会继续执行,不会等待oddThread和evenThread执行完成  }  
}

由于线程的执行是并发的,所以输出结果的顺序可能会有所不同,这取决于JVM的线程调度策略以及操作系统的线程管理机制。

优缺点 

优点:编码简单

缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展。

注意

创建方式二

通过实现Runnable接口方式 

1. 为什么使用Runnable接口?

在Java中,创建线程主要有两种方式:继承Thread类和实现Runnable接口。虽然继承Thread类是一种直观的方式,但它限制了类的继承体系(因为Java不支持多重继承)。相比之下,实现Runnable接口更为灵活,因为它允许你的类继承自其他类,并且仍然可以拥有多线程的能力。

2. Runnable接口简介

Runnable是一个函数式接口(从Java 8开始),它只定义了一个方法:run()。当你创建了一个实现了Runnable接口的类的实例后,你可以将这个实例作为参数传递给Thread类的构造函数,从而创建一个新的线程。

线程创建的基本步骤(使用Runnable接口)

①定义Runnable实现类
首先,你需要定义一个类来实现Runnable接口。实现接口意味着你必须提供run方法的实现。这个run方法将包含线程执行时所需的所有代码。

class MyTask implements Runnable {  @Override  public void run() {  // 在这里编写线程的任务代码  System.out.println(Thread.currentThread().getName() + " is running.");  // 假设这里有一些耗时的操作或逻辑处理  }  
}

②创建Runnable实现类的实例
一旦你定义了Runnable实现类,就可以创建这个类的实例了。这个实例将作为线程执行的任务。 

Runnable myTask = new MyTask();

③将Runnable实例传递给Thread构造函数
接下来,你需要将Runnable实例作为参数传递给Thread类的构造函数。这个构造函数会创建一个新的Thread对象,这个对象封装了Runnable实现类的实例。

Thread myThread = new Thread(myTask, "MyCustomThread");

注意,这里的第二个参数是可选的,用于指定线程的名称。如果省略,线程将使用默认名称。

启动线程
最后,你需要调用线程的start方法来启动线程。调用start方法会导致JVM调用Runnable实现类的run方法,在新的线程中执行。

myThread.start();

重要的是要区分start方法和run方法。start方法用于启动线程,而run方法则定义了线程执行的任务。如果你直接调用run方法(如myTask.run()),那么run方法中的代码将在当前线程中同步执行,而不是在新的线程中。

完整示例

将上述步骤组合起来,我们得到以下完整的示例:

class MyTask implements Runnable {  @Override  public void run() {  for (int i = 0; i < 5; i++) {  System.out.println(Thread.currentThread().getName() + " is running, iteration " + i);  try {  // 模拟耗时操作  Thread.sleep(1000);  } catch (InterruptedException e) {  e.printStackTrace();  }  }  }  
}  public class Main {  public static void main(String[] args) {  Runnable myTask = new MyTask();  // 创建并启动第一个线程  Thread thread1 = new Thread(myTask, "Thread-1");  thread1.start();  // 创建并启动第二个线程(注意:这里可以创建任意数量的线程)  Thread thread2 = new Thread(myTask, "Thread-2");  thread2.start();  // main线程继续执行其他任务或结束  System.out.println("Main thread is ending.");  }  
}

 在这个示例中,我们创建了两个线程(thread1thread2),它们都执行相同的任务(由MyTask类的run方法定义)。这两个线程将并发执行,各自打印出自己的迭代次数,并模拟了耗时操作(通过Thread.sleep)。同时,main线程在启动了两个线程后继续执行并结束,而不会影响或等待这两个子线程的执行。

优点

任务类只是实现接口,可以继续继承其他类、实现其它接口,扩展性强。

 

创建方式三 

通过Callable接口和FutureTask类来创建线程的方法

让我们详细讲解一下Java中通过Callable接口和FutureTask类来创建线程的方法,并以上面提到的类似代码来举例。

Callable接口

Callable接口与Runnable接口类似,都是为了被线程执行而设计的。但与Runnable不同的是,Callable可以返回值,并且它可以抛出异常。这使得Callable接口在某些场景下比Runnable更加灵活和强大。

FutureTask类

FutureTask类实现了FutureRunnable接口。它可以将CallableRunnable对象包装起来,以便有返回值的任务可以被提交给Executor执行。如果任务通过Callable包装,那么FutureTask将返回执行结果;如果通过Runnable包装,那么FutureTaskget()方法将返回null

构造器

  • public FutureTask<>(Callable<V> callable):这个构造器接受一个Callable<V>类型的参数。Callable是一个类似于Runnable的接口,但它可以返回一个结果并且可以抛出一个异常。FutureTask会将这个Callable对象封装成一个可以异步执行的任务。与Runnable不同,Callablecall方法可以有返回值,并且可以声明抛出异常。

方法

  • public V get() throws InterruptedException, ExecutionException:这个方法用于等待计算完成,并检索其结果。如果在计算完成之前调用此方法,它将会阻塞当前线程,直到计算完成。此方法会抛出两种类型的异常:
    • InterruptedException:如果当前线程在等待过程中被中断,则会抛出此异常。
    • ExecutionException:如果计算抛出异常,则会通过此异常包装并抛出。

示例代码

假设我们有一个任务,该任务需要计算一个整数的阶乘,并返回结果。我们可以使用Callable接口来定义这个任务,并使用FutureTask来包装它,以便可以提交给线程池执行。


public class FactorialCallable implements Callable<Long> {  private int number;  public FactorialCallable(int number) {  this.number = number;  }  //重写call方法@Override  public Long call() throws Exception {  //描述线程的任务,返回执行后的结果long result = 1;  for (int i = 1; i <= number; i++) {  result *= i;  }  return result;  }  
}  public class DirectThreadExample {  public static void main(String[] args) {  // 创建 Callable 任务实例  FactorialCallable task = new FactorialCallable(5);  // 由于 Thread 不能直接执行 Callable,我们需要将 Callable 包装为 FutureTask  FutureTask<Long> futureTask = new FutureTask<>(task);  // 创建一个新的 Thread,并将 FutureTask 作为其任务  Thread thread = new Thread(futureTask);  // 启动线程  thread.start();  try {  // 等待任务完成并获取结果  Long result = futureTask.get(); // 这会阻塞当前线程直到任务完成  System.out.println("Factorial of 5 is: " + result);  } catch (InterruptedException | ExecutionException e) {  e.printStackTrace();  }  // 注意:在实际应用中,你可能需要处理线程的中断和优雅关闭等逻辑  }  
}

优点

线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。

可以在线程执行完毕后去获取线程执行的结果。

 

这篇关于如何在程序中创建出多条线程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1107389

相关文章

解决IDEA使用springBoot创建项目,lombok标注实体类后编译无报错,但是运行时报错问题

《解决IDEA使用springBoot创建项目,lombok标注实体类后编译无报错,但是运行时报错问题》文章详细描述了在使用lombok的@Data注解标注实体类时遇到编译无误但运行时报错的问题,分析... 目录问题分析问题解决方案步骤一步骤二步骤三总结问题使用lombok注解@Data标注实体类,编译时

MySQL分表自动化创建的实现方案

《MySQL分表自动化创建的实现方案》在数据库应用场景中,随着数据量的不断增长,单表存储数据可能会面临性能瓶颈,例如查询、插入、更新等操作的效率会逐渐降低,分表是一种有效的优化策略,它将数据分散存储在... 目录一、项目目的二、实现过程(一)mysql 事件调度器结合存储过程方式1. 开启事件调度器2. 创

mysql外键创建不成功/失效如何处理

《mysql外键创建不成功/失效如何处理》文章介绍了在MySQL5.5.40版本中,创建带有外键约束的`stu`和`grade`表时遇到的问题,发现`grade`表的`id`字段没有随着`studen... 当前mysql版本:SELECT VERSION();结果为:5.5.40。在复习mysql外键约

Window Server创建2台服务器的故障转移群集的图文教程

《WindowServer创建2台服务器的故障转移群集的图文教程》本文主要介绍了在WindowsServer系统上创建一个包含两台成员服务器的故障转移群集,文中通过图文示例介绍的非常详细,对大家的... 目录一、 准备条件二、在ServerB安装故障转移群集三、在ServerC安装故障转移群集,操作与Ser

Window Server2016 AD域的创建的方法步骤

《WindowServer2016AD域的创建的方法步骤》本文主要介绍了WindowServer2016AD域的创建的方法步骤,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录一、准备条件二、在ServerA服务器中常见AD域管理器:三、创建AD域,域地址为“test.ly”

Python在固定文件夹批量创建固定后缀的文件(方法详解)

《Python在固定文件夹批量创建固定后缀的文件(方法详解)》文章讲述了如何使用Python批量创建后缀为.md的文件夹,生成100个,代码中需要修改的路径、前缀和后缀名,并提供了注意事项和代码示例,... 目录1. python需求的任务2. Python代码的实现3. 代码修改的位置4. 运行结果5.

使用IntelliJ IDEA创建简单的Java Web项目完整步骤

《使用IntelliJIDEA创建简单的JavaWeb项目完整步骤》:本文主要介绍如何使用IntelliJIDEA创建一个简单的JavaWeb项目,实现登录、注册和查看用户列表功能,使用Se... 目录前置准备项目功能实现步骤1. 创建项目2. 配置 Tomcat3. 项目文件结构4. 创建数据库和表5.

使用SpringBoot创建一个RESTful API的详细步骤

《使用SpringBoot创建一个RESTfulAPI的详细步骤》使用Java的SpringBoot创建RESTfulAPI可以满足多种开发场景,它提供了快速开发、易于配置、可扩展、可维护的优点,尤... 目录一、创建 Spring Boot 项目二、创建控制器类(Controller Class)三、运行

Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单

《Springboot的ThreadPoolTaskScheduler线程池轻松搞定15分钟不操作自动取消订单》:本文主要介绍Springboot的ThreadPoolTaskScheduler线... 目录ThreadPoolTaskScheduler线程池实现15分钟不操作自动取消订单概要1,创建订单后

JAVA中整型数组、字符串数组、整型数和字符串 的创建与转换的方法

《JAVA中整型数组、字符串数组、整型数和字符串的创建与转换的方法》本文介绍了Java中字符串、字符数组和整型数组的创建方法,以及它们之间的转换方法,还详细讲解了字符串中的一些常用方法,如index... 目录一、字符串、字符数组和整型数组的创建1、字符串的创建方法1.1 通过引用字符数组来创建字符串1.2