Java进阶篇之线程的创建和运行

引言

在前面的文章中,我们介绍了多线程(Java进阶篇之多线程)。在开发高效的并发应用时,线程的创建与运行是我们需要掌握的基本技能。线程能够让我们同时处理多个任务,充分利用多核 CPU 提高程序的执行效率。Java 提供了多种方式来创建和启动线程,从简单的继承 Thread 类到实现 Runnable 接口,再到使用现代的 ExecutorService,每种方式都有其适用的场景。

今天,我们就来聊一聊 Java 中线程的创建与运行,如何在不同的场景下灵活运用这些方式,提升你的多线程编程水平,让你的程序更快、更高效!


目录

引言

一、线程的基础概念

线程的生命周期 ⏳

二、线程的创建与运行方式 ‍♂️

1️⃣ 继承 Thread 类

2️⃣ 实现 Runnable 接口 ‍♂️

3️⃣ 使用 ExecutorService 线程池 ️

三、线程的优先级 ⚖️

四、线程的常见问题与调试 ‍

1️⃣ 线程安全

2️⃣ 死锁

3️⃣ 线程饥饿与活锁

五、总结

思维导图


一、线程的基础概念

在 Java 中,线程 是程序执行的最小单位。线程有自己的运行环境,如独立的栈和程序计数器。多个线程共享堆内存数据,每个线程都有自己的执行路径。

  • 主线程:每个 Java 程序都有一个主线程,通常是 main 方法执行的线程。
  • 子线程:通过创建新线程来执行其他任务,通常用于处理并行任务,提升效率。
线程的生命周期 ⏳

线程的生命周期有几个状态,下面是线程的状态转换图:

Java进阶篇之线程的创建和运行_第1张图片

  • 新建:线程对象已创建,尚未调用 start()
  • 可运行:调用 start() 后,线程处于可执行状态,等待操作系统调度执行。
  • 运行中:线程正在执行 run() 方法。
  • 阻塞:线程处于等待状态,例如等待某个资源。
  • 死亡:线程完成任务或被终止。

二、线程的创建与运行方式 ‍♂️

Java 中有三种常见的方式来创建线程:继承 Thread 类、实现 Runnable 接口和使用线程池 ExecutorService。我们来对比一下这三种方式,看看它们的优缺点和适用场景。

方式 优点 缺点 适用场景
继承 Thread 简单易用,适用于任务较简单的情况 继承限制,不能再继承其他类 适用于单一任务的简单应用
实现 Runnable 接口 可以实现多个线程共享同一任务,灵活性高 不支持直接获取线程的返回值 适用于需要多个线程执行相同任务的场景
使用 ExecutorService 高效管理线程池,适用于大量任务的并发执行 相对复杂,使用不当可能导致资源泄露 适用于高并发、大量任务的场景
1️⃣ 继承 Thread

继承 Thread 类并重写 run() 方法是最简单的方式。在这个方式中,每个线程都对应一个 Thread 类的实例。

示例:继承 Thread 类创建线程

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread " + Thread.currentThread().getId() + " is running!");
    }
}

public class ThreadExample {
    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        thread1.start();  // 启动线程

        MyThread thread2 = new MyThread();
        thread2.start();  // 启动线程
    }
}
  • start() 方法用于启动线程,线程一旦启动就进入 可运行 状态。
  • run() 方法是线程的执行体,线程启动后会执行此方法。
2️⃣ 实现 Runnable 接口 ‍♂️

相比继承 Thread,实现 Runnable 接口可以让我们实现更灵活的多线程应用。多个线程可以共享同一个 Runnable 实例,尤其适合多个线程执行相同任务的场景。

示例:实现 Runnable 接口创建线程

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Thread " + Thread.currentThread().getId() + " is running!");
    }
}

public class RunnableExample {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread1 = new Thread(myRunnable);
        thread1.start();  // 启动线程

        Thread thread2 = new Thread(myRunnable);
        thread2.start();  // 启动线程
    }
}
  • 通过实现 Runnable 接口,多个线程可以共享同一个 Runnable 实例,这在任务相同的场景下非常有用。
  • run() 方法包含了线程要执行的代码。
3️⃣ 使用 ExecutorService 线程池 ️

当我们需要创建大量线程时,直接通过 Thread 类或 Runnable 接口来管理线程会显得低效和繁琐。ExecutorService 提供了一种更高效的线程管理方式,能够复用线程池中的线程,避免线程的频繁创建和销毁。

示例:使用 ExecutorService 创建线程池

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

public class ExecutorServiceExample {
    public static void main(String[] args) {
        // 创建一个固定大小为 3 的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        // 提交任务给线程池
        for (int i = 0; i < 5; i++) {
            executorService.submit(() -> {
                System.out.println("Thread " + Thread.currentThread().getId() + " is executing task");
            });
        }

        // 关闭线程池
        executorService.shutdown();
    }
}
  • 线程池 ExecutorService 允许我们创建一个固定数量的线程,并复用它们来执行多个任务。
  • 使用 submit() 方法将任务提交给线程池,线程池中的线程会并发执行这些任务。

三、线程的优先级 ⚖️

线程的优先级决定了线程在 CPU 中的调度顺序。Java 中的线程有 10 个优先级,数字越大,线程的优先级越高。默认情况下,线程的优先级为 5。

优先级设置可以影响多线程程序的性能,但我们通常不建议过度依赖它,因为操作系统的调度器会决定线程的实际执行顺序。

示例:设置线程优先级

class PriorityThread extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getId() + " has priority: " + getPriority());
    }
}

public class ThreadPriorityExample {
    public static void main(String[] args) {
        Thread thread1 = new PriorityThread();
        thread1.setPriority(Thread.MIN_PRIORITY);  // 设置最低优先级
        thread1.start();

        Thread thread2 = new PriorityThread();
        thread2.setPriority(Thread.MAX_PRIORITY);  // 设置最高优先级
        thread2.start();

        Thread thread3 = new PriorityThread();
        thread3.setPriority(Thread.NORM_PRIORITY);  // 默认优先级
        thread3.start();
    }
}
  • 通过 setPriority() 方法设置线程的优先级。
  • 可以选择 Thread.MIN_PRIORITY(1)、Thread.MAX_PRIORITY(10)、Thread.NORM_PRIORITY(5)来设置优先级。

四、线程的常见问题与调试 ‍

1️⃣ 线程安全

当多个线程同时访问共享数据时,可能会出现数据竞争问题,导致程序错误。为了解决这个问题,我们通常使用同步机制(synchronized)来保证线程安全。

示例:使用 synchronized 保证线程安全

class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}
2️⃣ 死锁

死锁是指两个或多个线程互相等待对方释放资源,导致程序永远无法继续执行。避免死锁的策略包括:

  • 避免嵌套锁:尽量避免在持有一个锁的情况下去获取其他锁。
  • 锁排序:按照固定的顺序来获取锁,避免循环等待。
3️⃣ 线程饥饿与活锁
  • 线程饥饿:某些线程长时间无法获得执行机会,导致无法完成任务。
  • 活锁:线程虽然一直在执行任务,但并未完成预期的工作。可以通过合理调度任务、减少不必要的锁竞争来解决。

思维导图

Java进阶篇之线程的创建和运行_第2张图片


五、总结

通过今天的学习,你已经掌握了 Java 中线程的创建与运行方式,从继承 Thread 类到实现 Runnable 接口,再到使用线程池 ExecutorService,每种方式都有其独特的优势和适用场景。理解并灵活运用这些方式,将大大提高你的并发编程能力!

  • Thread:适合简单的线程创建。
  • Runnable 接口:适合多个线程共享任务。
  • ExecutorService:适用于高并发、大量任务的线程管理。

在接下来的文章中,我们将继续探讨Java中的同步和锁以及其他重要特性,敬请期待!

 如果你觉得这篇文章对你有所帮助,欢迎点赞、收藏、分享!


你可能感兴趣的:(Java,Java进阶,java,开发语言,intellij-idea,eclipse,ide)