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

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

相关文章

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

在cscode中通过maven创建java项目

在cscode中创建java项目 可以通过博客完成maven的导入 建立maven项目 使用快捷键 Ctrl + Shift + P 建立一个 Maven 项目 1 Ctrl + Shift + P 打开输入框2 输入 "> java create"3 选择 maven4 选择 No Archetype5 输入 域名6 输入项目名称7 建立一个文件目录存放项目,文件名一般为项目名8 确定

Java 创建图形用户界面(GUI)入门指南(Swing库 JFrame 类)概述

概述 基本概念 Java Swing 的架构 Java Swing 是一个为 Java 设计的 GUI 工具包,是 JAVA 基础类的一部分,基于 Java AWT 构建,提供了一系列轻量级、可定制的图形用户界面(GUI)组件。 与 AWT 相比,Swing 提供了许多比 AWT 更好的屏幕显示元素,更加灵活和可定制,具有更好的跨平台性能。 组件和容器 Java Swing 提供了许多

EMLOG程序单页友链和标签增加美化

单页友联效果图: 标签页面效果图: 源码介绍 EMLOG单页友情链接和TAG标签,友链单页文件代码main{width: 58%;是设置宽度 自己把设置成与您的网站宽度一样,如果自适应就填写100%,TAG文件不用修改 安装方法:把Links.php和tag.php上传到网站根目录即可,访问 域名/Links.php、域名/tag.php 所有模板适用,代码就不粘贴出来,已经打

顺序表之创建,判满,插入,输出

文章目录 🍊自我介绍🍊创建一个空的顺序表,为结构体在堆区分配空间🍊插入数据🍊输出数据🍊判断顺序表是否满了,满了返回值1,否则返回0🍊main函数 你的点赞评论就是对博主最大的鼓励 当然喜欢的小伙伴可以:点赞+关注+评论+收藏(一键四连)哦~ 🍊自我介绍   Hello,大家好,我是小珑也要变强(也是小珑),我是易编程·终身成长社群的一名“创始团队·嘉宾”

跨系统环境下LabVIEW程序稳定运行

在LabVIEW开发中,不同电脑的配置和操作系统(如Win11与Win7)可能对程序的稳定运行产生影响。为了确保程序在不同平台上都能正常且稳定运行,需要从兼容性、驱动、以及性能优化等多个方面入手。本文将详细介绍如何在不同系统环境下,使LabVIEW开发的程序保持稳定运行的有效策略。 LabVIEW版本兼容性 LabVIEW各版本对不同操作系统的支持存在差异。因此,在开发程序时,尽量使用

Maven创建项目中的groupId, artifactId, 和 version的意思

文章目录 groupIdartifactIdversionname groupId 定义:groupId 是 Maven 项目坐标的第一个部分,它通常表示项目的组织或公司的域名反转写法。例如,如果你为公司 example.com 开发软件,groupId 可能是 com.example。作用:groupId 被用来组织和分组相关的 Maven artifacts,这样可以避免

CSP 2023 提高级第一轮 CSP-S 2023初试题 完善程序第二题解析 未完

一、题目阅读 (最大值之和)给定整数序列 a0,⋯,an−1,求该序列所有非空连续子序列的最大值之和。上述参数满足 1≤n≤105 和 1≤ai≤108。 一个序列的非空连续子序列可以用两个下标 ll 和 rr(其中0≤l≤r<n0≤l≤r<n)表示,对应的序列为 al,al+1,⋯,ar​。两个非空连续子序列不同,当且仅当下标不同。 例如,当原序列为 [1,2,1,2] 时,要计算子序列 [

这些心智程序你安装了吗?

原文题目:《为什么聪明人也会做蠢事(四)》 心智程序 大脑有两个特征导致人类不够理性,一个是处理信息方面的缺陷,一个是心智程序出了问题。前者可以称为“认知吝啬鬼”,前几篇文章已经讨论了。本期主要讲心智程序这个方面。 心智程序这一概念由哈佛大学认知科学家大卫•帕金斯提出,指个体可以从记忆中提取出的规则、知识、程序和策略,以辅助我们决策判断和解决问题。如果把人脑比喻成计算机,那心智程序就是人脑的