Java 多线程创建零基础入门新手指南:从零开始全面学习多线程创建方法

简介: 本文从零基础角度出发,深入浅出地讲解Java多线程的创建方式。内容涵盖继承`Thread`类、实现`Runnable`接口、使用`Callable`和`Future`接口以及线程池的创建与管理等核心知识点。通过代码示例与应用场景分析,帮助读者理解每种方式的特点及适用场景,理论结合实践,轻松掌握Java多线程编程 essentials。

我将从基础概念讲起,逐步深入介绍Java多线程的创建方式,通过代码示例帮助你理解,并阐述每种方式的适用场景。

Java多线程新手指南:从零开始学习多线程创建

多线程已是众人眼里老生常谈的话题之一了,在日常项目开发中它也是最为熟知且常用的技术之一,毕竟它能够允许程序同时执行多个任务,从而提高程序的响应速度和资源利用率,顾名思义,就是让计算机的多个核心同时工作,就像多个人同时做不同的任务一样。但在Java这种流行的编程语言中,多线程的使用尤为重要,因为它能帮助我们写出更高效、更流畅的程序,毕竟Java作为一种广泛应用于开发领域的语言,提供了强大的多线程支持,也提供了丰富的多线程支持,使得开发者可以轻松地实现并发编程。所以今天我继续来聊聊它,本文将重点介绍及演示如何使用Java来创建多线程,及衍生穿插知识点,带着大家能零基础轻松入门多线程编辑。

在Java多线程编程中,掌握线程的创建和管理是至关重要的。本文将介绍Java中多线程的创建方法,包括继承Thread类和实现Runnable接口两种方式,并通过源代码解析、应用场景案例、优缺点分析等来详细讲解每一种方法的使用和特点,最后通过一个测试案例来完整的复盘一遍,理论+实践 = 百分百掌握。

一、为什么需要多线程?

可能很多刚入门的小伙伴就会产生疑问,为啥需要多线程?这里我先就给大家进行分享下个人的理解,无妨大家可以这样想象一下,你在厨房里准备晚餐,如果只有你一个人,你可能需要先切菜,然后煮饭,接着炒菜;但如果你有帮手,你们可以同时进行这些任务,这样晚餐就能更快准备好。同样,在计算机程序中,如果我们能让不同的任务同时进行,程序就能更快地完成工作,也就是所谓的并行处理,同一时间多人进行,从而加快程序的执行速度。

多线程非常重要,它就像是在厨房里多请了几个帮手,每个人都能同时做不同的活儿,这样饭菜就能更快上桌。在电脑程序里,多线程能让程序同时做很多事,就像多个人一起工作一样,效率自然就高了。其具体优势如下:

  1. 提高效率:想象一下,如果电脑里只有一个程序在运行,它就只能做一件事。但有了多线程,电脑就能像多核大脑一样,同时处理好几个任务,速度自然就快了。
  2. 改善响应性:就像你在玩游戏时,如果电脑还在下载东西,游戏可能会卡顿。但如果用多线程,下载和游戏可以同时进行,互不影响,游戏就能流畅多了。
  3. 资源利用率:多线程就像是把电脑的CPU和内存都用起来,不让它们闲着。这样,电脑的每个部分都能发挥最大作用。
  4. 更好的用户体验:用户就像是顾客,他们希望点菜后能快速上菜。多线程能让程序快速响应用户的操作,就像快速上菜一样,让顾客满意。
  5. 并行处理:有些任务可以分成很多小块,每块都可以同时处理。多线程就像是有很多工人同时工作,这样完成任务的速度就快多了。
  6. 简化设计:有时候,程序设计得太复杂,就像厨房里有很多复杂的机器。多线程可以让程序设计更简单,就像用简单的工具就能做出好菜。
  7. 避免阻塞:如果一个任务卡住了,整个程序可能就会停下来。多线程可以避免这种情况,就像一个厨师忙不过来,其他厨师可以接手继续做。
  8. 利用现代硬件:现在的电脑越来越强大,多线程就像是让这些强大的电脑发挥出它们的最大能力。
  9. 适应性:在一些需要快速反应的场合,比如在线游戏或者股票交易,多线程能让程序更加灵活,快速适应变化。
  10. 错误隔离:如果程序中的一个部分出了问题,多线程可以保证其他部分不受影响,就像一个厨师出错了,其他厨师还可以继续工作。

二、Java中的多线程是如何工作的?

Java提供了两种主要的方式来创建线程,这两种方式各有特点,适用于不同的应用场景:

  1. 继承Thread类:就像你创建一个新食谱,基于一个已有的食谱进行修改。在Java中,你可以通过创建一个新的类,继承自Thread类,并重写它的run方法来定义你的任务。
  2. 实现Runnable接口:这就像是按照食谱做菜,你不需要自己从头开始创造食谱,只需要按照已有的步骤来。在Java中,你可以创建一个实现了Runnable接口的类,并实现它的run方法,然后将其传递给Thread对象。

三、如何创建和启动线程?

创建和启动线程是多线程编程中的基础操作,它们确保了程序能够并行执行多个任务。以下是创建和启动线程的详细步骤,以及它们之间的逻辑联系:

  1. 定义任务:在多线程编程中,每个线程都需要执行一个特定的任务。任务的定义通常通过编写一个run方法来实现。这个方法是线程执行的入口点,包含了线程要执行的所有操作。
  2. 选择线程创建方式:根据你的程序需求,你可以选择继承Thread类或实现Runnable接口来定义你的任务。继承Thread类意味着你的类直接扩展了线程的功能,而实现Runnable接口则需要将你的任务类传递给一个Thread对象。
  3. 创建线程对象:一旦你定义了任务,接下来就是创建线程对象。如果你选择继承Thread类,你将创建该类的实例;如果实现Runnable接口,你需要创建一个Runnable对象,并将该对象作为参数传递给Thread类的构造函数,然后创建Thread对象的实例。
  4. 启动线程:创建线程对象后,你需要调用start()方法来启动线程。这个方法会触发线程的执行,使其进入就绪状态,并最终运行。重要的是要注意,start()方法会隐式地调用你的run方法,因此无需手动调用run
  5. 线程的执行:一旦线程启动,它将按照定义的run方法中的指令执行任务。线程的执行是由Java运行时环境的线程调度器控制的,它会根据系统的线程调度策略来决定哪个线程何时执行。

四、Java多线程创建方式详解

4.1 继承Thread类

这是创建线程最直接的方式之一。通过继承Thread类,并重写其run方法,来定义线程的执行逻辑。

4.1.1 创建步骤

  1. 定义线程类:创建一个类并继承自Thread类。在这个类中,重写run()方法,run()方法中的代码就是这个线程要执行的任务。
    class MyThread extends Thread {
         
     @Override
     public void run() {
         
         for (int i = 0; i < 5; i++) {
         
             System.out.println("线程 " + getName() + " 执行,i = " + i);
         }
     }
    }
    
  2. 创建并启动线程:在主程序中创建线程对象,并调用start()方法来启动线程。start()方法会自动调用线程对象的run()方法,使得线程开始执行。例如:

    public class Main {
         
     public static void main(String[] args) {
         
         MyThread thread1 = new MyThread();
         thread1.start();
    
         MyThread thread2 = new MyThread();
         thread2.start();
     }
    }
    

    4.1.2 优缺点分析

  • 优点:简单直接,符合面向对象的编程思想,初学者容易理解和掌握。
  • 缺点:由于Java是单继承的,如果一个类已经继承了其他类,就无法再继承Thread类来创建线程,这限制了类的扩展性。同时,线程与任务逻辑耦合在一起,不符合“组合优于继承”的设计原则。

4.1.3 应用场景案例

适合一些简单的、独立的任务,不需要继承其他类的情况。比如,在一个小型的测试程序中,需要创建几个简单的线程来模拟并发操作,此时使用继承Thread类的方式就比较合适。

4.2 实现Runnable接口

这是另一种常见的创建线程的方式,通过实现Runnable接口来定义任务,然后将其传递给Thread对象。

4.2.1 创建步骤

  1. 定义实现Runnable接口的类:创建一个类实现Runnable接口,然后实现接口中的run()方法,将线程要执行的任务写在run()方法中。例如:
    class MyRunnable implements Runnable {
         
     @Override
     public void run() {
         
         for (int i = 0; i < 5; i++) {
         
             System.out.println("线程 " + Thread.currentThread().getName() + " 执行,i = " + i);
         }
     }
    }
    
  2. 创建并启动线程:首先创建实现了Runnable接口的类的对象,然后将这个对象作为参数传递给Thread类的构造函数来创建线程对象,最后调用start()方法启动线程。例如:

    public class Main {
         
     public static void main(String[] args) {
         
         MyRunnable runnable1 = new MyRunnable();
         Thread thread1 = new Thread(runnable1);
         thread1.start();
    
         MyRunnable runnable2 = new MyRunnable();
         Thread thread2 = new Thread(runnable2);
         thread2.start();
     }
    }
    

    4.2.2 优缺点分析

  • 优点:避免了单继承的限制,因为一个类可以实现多个接口。同时,多个线程可以共享同一个Runnable对象,适合多个线程执行相同任务的情况,提高了代码的复用性。任务与线程分离,更符合“组合优于继承”的设计原则,使得代码的结构更加清晰。
  • 缺点:相比继承Thread类,代码稍微复杂一些,需要创建Runnable对象并将其传递给Thread对象。

4.2.3 应用场景案例

在多线程的场景中,如果有多个线程需要执行相同的任务逻辑,例如在一个服务器程序中,多个客户端请求可能需要执行相同的业务逻辑,此时使用实现Runnable接口的方式就非常合适。可以创建一个Runnable实现类,然后为每个客户端请求创建一个新的线程来执行该Runnable任务。

4.3 使用Callable和Future接口(适用于有返回值的线程)

前面两种方式创建的线程,其执行结果是无法直接获取的。如果需要获取线程执行后的返回值,可以使用CallableFuture接口。

4.3.1 创建步骤

  1. 定义实现Callable接口的类:创建一个类实现Callable接口,需要指定返回值类型。实现call()方法,在这个方法中编写线程要执行的任务,并返回一个结果。例如,计算1到100的整数和:
    ```java
    import java.util.concurrent.Callable;

class MyCallable implements Callable {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
}
}

2. **创建并执行线程,获取结果**:通过`ExecutorService`来管理线程的执行。首先创建`Callable`对象,然后将其提交给`ExecutorService`,返回一个`Future`对象。通过`Future`对象可以获取线程执行后的返回值。例如:
```java
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 {
        MyCallable callable = new MyCallable();
        // 创建一个线程池,这里只使用一个线程来执行任务,实际可以根据需求调整线程数量
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<Integer> future = executor.submit(callable);
        // 获取线程执行后的结果
        Integer result = future.get();
        System.out.println("1到100的整数和为:" + result);
        // 关闭线程池
        executor.shutdown();
    }
}

4.3.2 优缺点分析

  • 优点:可以获取线程执行后的返回值,并且可以通过ExecutorService来更好地管理线程,如设置线程池大小、控制线程的执行顺序等,提高了线程的管理和控制能力。
  • 缺点:相比前两种方式,代码更加复杂,需要理解ExecutorServiceCallableFuture等多个接口和类的使用,对初学者来说难度较大。

4.3.3 应用场景案例

在一些需要异步计算并获取结果的场景中非常有用。比如,在一个数据分析程序中,需要启动一个线程去执行复杂的数据计算任务,计算完成后获取计算结果进行后续处理。此时使用CallableFuture接口就可以满足需求。

4.4 使用线程池创建多线程

线程池是一种管理和复用线程的机制,可以避免频繁创建和销毁线程带来的开销,提高系统性能。

4.4.1 使用ExecutorService和Executors创建线程池

  1. 创建线程池:可以使用Executors工厂类来创建不同类型的线程池。例如,创建一个固定大小的线程池:
    // 创建一个包含5个线程的固定大小线程池
    ExecutorService executorService = Executors.newFixedThreadPool(5);
    
    Executors还可以创建其他类型的线程池,如CachedThreadPool(根据需要创建线程,线程空闲一段时间后会被回收)、SingleThreadExecutor(只有一个线程的线程池)等。
  2. 定义任务(可以是实现Runnable接口或Callable接口的类):例如,定义一个实现Runnable接口的任务类:
    class MyTask implements Runnable {
         
     @Override
     public void run() {
         
         System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行任务");
     }
    }
    
  3. 提交任务到线程池:将任务提交给线程池执行。对于实现Runnable接口的任务,可以使用execute()方法提交;对于实现Callable接口的任务(用于有返回值的情况),可以使用submit()方法提交。以下是提交Runnable任务的示例:
    ExecutorService executorService = Executors.newFixedThreadPool(5);
    // 创建任务对象
    MyTask task = new MyTask();
    // 提交任务到线程池,这个任务会被线程池中的某个线程执行
    executorService.execute(task);
    // 关闭线程池,不再接受新任务,但会等待已提交的任务执行完毕
    executorService.shutdown();
    
  4. 关闭线程池(可选):当任务都提交完毕后,可以关闭线程池。shutdown()方法会平滑地关闭线程池,它会等待所有已提交的任务执行完毕后再关闭线程池。如果希望立即关闭线程池,不等待未完成的任务,可以使用shutdownNow()方法,但这种方式可能会导致正在执行的任务被中断。

4.4.2 自定义线程池(使用ThreadPoolExecutor类)

除了使用Executors工厂类创建线程池外,还可以通过ThreadPoolExecutor类来自定义线程池的参数,以满足更复杂的需求。

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class Main {
   
    public static void main(String[] args) {
   
        // 核心线程数
        int corePoolSize = 2;
        // 最大线程数
        int maximumPoolSize = 4;
        // 线程空闲时间
        long keepAliveTime = 10;
        // 时间单位
        TimeUnit unit = TimeUnit.SECONDS;
        // 任务队列
        BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(10);

        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                unit,
                workQueue);

        for (int i = 0; i < 15; i++) {
   
            final int taskNumber = i;
            executor.submit(() -> {
   
                System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行任务 " + taskNumber);
                try {
   
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
   
                    e.printStackTrace();
                }
            });
        }

        executor.shutdown();
    }
}

在这个示例中,我们创建了一个自定义的线程池,通过ThreadPoolExecutor的构造函数设置了核心线程数、最大线程数、线程空闲时间、任务队列等参数。

4.4.3 线程池的优缺点分析

  • 优点
    • 提高性能:避免了频繁创建和销毁线程带来的开销,线程池中的线程可以复用,减少了线程创建和销毁的时间和资源消耗。
    • 控制并发数:可以通过设置线程池的大小来控制并发执行的线程数量,避免过多的线程竞争

Java, 多线程,线程创建,零基础入门,新手教程,并发编程,线程池,Thread 类,Runnable 接口,Callable 接口,Future, 多线程同步,volatile, 锁机制,Java 多线程实战



资源地址:
https://pan.quark.cn/s/14fcf913bae6


相关文章
|
24天前
|
存储 Oracle Java
java零基础学习者入门课程
本课程为Java零基础入门教程,涵盖环境搭建、变量、运算符、条件循环、数组及面向对象基础,每讲配示例代码与实践建议,助你循序渐进掌握核心知识,轻松迈入Java编程世界。
215 0
|
2月前
|
Java API 容器
Java基础学习day08-2
本节讲解Java方法引用与常用API,包括静态、实例、特定类型方法及构造器引用的格式与使用场景,并结合代码示例深入解析。同时介绍String和ArrayList的核心方法及其实际应用。
143 1
|
1月前
|
IDE Java 编译器
java编程最基础学习
Java入门需掌握:环境搭建、基础语法、面向对象、数组集合与异常处理。通过实践编写简单程序,逐步深入学习,打牢编程基础。
163 0
|
30天前
|
Java
Java语言实现字母大小写转换的方法
Java提供了多种灵活的方法来处理字符串中的字母大小写转换。根据具体需求,可以选择适合的方法来实现。在大多数情况下,使用 String类或 Character类的方法已经足够。但是,在需要更复杂的逻辑或处理非常规字符集时,可以通过字符流或手动遍历字符串来实现更精细的控制。
203 18
|
1月前
|
负载均衡 Java API
grpc-java 架构学习指南
本指南系统解析 grpc-java 架构,涵盖分层设计、核心流程与源码结构,结合实战路径与调试技巧,助你从入门到精通,掌握高性能 RPC 开发精髓。
172 7
|
1月前
|
Java 编译器 Go
【Java】(5)方法的概念、方法的调用、方法重载、构造方法的创建
Java方法是语句的集合,它们在一起执行一个功能。方法是解决一类问题的步骤的有序组合方法包含于类或对象中方法在程序中被创建,在其他地方被引用方法的优点使程序变得更简短而清晰。有利于程序维护。可以提高程序开发的效率。提高了代码的重用性。方法的名字的第一个单词应以小写字母作为开头,后面的单词则用大写字母开头写,不使用连接符。例如:addPerson。这种就属于驼峰写法下划线可能出现在 JUnit 测试方法名称中用以分隔名称的逻辑组件。
173 4
|
1月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
119 1
|
1月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
135 1
|
2月前
|
算法 安全 Java
除了类,Java中的接口和方法也可以使用泛型吗?
除了类,Java中的接口和方法也可以使用泛型吗?
123 11
|
1月前
|
编解码 Java 开发者
Java String类的关键方法总结
以上总结了Java `String` 类最常见和重要功能性方法。每种操作都对应着日常编程任务,并且理解每种操作如何影响及处理 `Strings` 对于任何使用 Java 的开发者来说都至关重要。
233 5