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

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

相关文章

基于Python开发PDF转Doc格式小程序

《基于Python开发PDF转Doc格式小程序》这篇文章主要为大家详细介绍了如何基于Python开发PDF转Doc格式小程序,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 用python实现PDF转Doc格式小程序以下是一个使用Python实现PDF转DOC格式的GUI程序,采用T

Python中conda虚拟环境创建及使用小结

《Python中conda虚拟环境创建及使用小结》本文主要介绍了Python中conda虚拟环境创建及使用小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们... 目录0.前言1.Miniconda安装2.conda本地基本操作3.创建conda虚拟环境4.激活c

Spring Boot 中正确地在异步线程中使用 HttpServletRequest的方法

《SpringBoot中正确地在异步线程中使用HttpServletRequest的方法》文章讨论了在SpringBoot中如何在异步线程中正确使用HttpServletRequest的问题,... 目录前言一、问题的来源:为什么异步线程中无法访问 HttpServletRequest?1. 请求上下文与线

在 Spring Boot 中使用异步线程时的 HttpServletRequest 复用问题记录

《在SpringBoot中使用异步线程时的HttpServletRequest复用问题记录》文章讨论了在SpringBoot中使用异步线程时,由于HttpServletRequest复用导致... 目录一、问题描述:异步线程操作导致请求复用时 Cookie 解析失败1. 场景背景2. 问题根源二、问题详细分

使用Python创建一个能够筛选文件的PDF合并工具

《使用Python创建一个能够筛选文件的PDF合并工具》这篇文章主要为大家详细介绍了如何使用Python创建一个能够筛选文件的PDF合并工具,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录背景主要功能全部代码代码解析1. 初始化 wx.Frame 窗口2. 创建工具栏3. 创建布局和界面控件4

将java程序打包成可执行文件的实现方式

《将java程序打包成可执行文件的实现方式》本文介绍了将Java程序打包成可执行文件的三种方法:手动打包(将编译后的代码及JRE运行环境一起打包),使用第三方打包工具(如Launch4j)和JDK自带... 目录1.问题提出2.如何将Java程序打包成可执行文件2.1将编译后的代码及jre运行环境一起打包2

Java中对象的创建和销毁过程详析

《Java中对象的创建和销毁过程详析》:本文主要介绍Java中对象的创建和销毁过程,对象的创建过程包括类加载检查、内存分配、初始化零值内存、设置对象头和执行init方法,对象的销毁过程由垃圾回收机... 目录前言对象的创建过程1. 类加载检查2China编程. 分配内存3. 初始化零值4. 设置对象头5. 执行

Android 悬浮窗开发示例((动态权限请求 | 前台服务和通知 | 悬浮窗创建 )

《Android悬浮窗开发示例((动态权限请求|前台服务和通知|悬浮窗创建)》本文介绍了Android悬浮窗的实现效果,包括动态权限请求、前台服务和通知的使用,悬浮窗权限需要动态申请并引导... 目录一、悬浮窗 动态权限请求1、动态请求权限2、悬浮窗权限说明3、检查动态权限4、申请动态权限5、权限设置完毕后

Java多线程父线程向子线程传值问题及解决

《Java多线程父线程向子线程传值问题及解决》文章总结了5种解决父子之间数据传递困扰的解决方案,包括ThreadLocal+TaskDecorator、UserUtils、CustomTaskDeco... 目录1 背景2 ThreadLocal+TaskDecorator3 RequestContextH

在不同系统间迁移Python程序的方法与教程

《在不同系统间迁移Python程序的方法与教程》本文介绍了几种将Windows上编写的Python程序迁移到Linux服务器上的方法,包括使用虚拟环境和依赖冻结、容器化技术(如Docker)、使用An... 目录使用虚拟环境和依赖冻结1. 创建虚拟环境2. 冻结依赖使用容器化技术(如 docker)1. 创