多线程与并发-理论基础

1 基础概念

1.1 进程和线程

1.1.1 进程(Process)

进程 指的是程序在操作系统里的一次执行过程,它是系统进行资源分配和调度的基本单位。进程具备自己独立的内存空间、系统资源以及执行上下文。下面是进程的一些主要特点:

  1. 独立性 :不同的进程之间相互隔离,一个进程无法直接访问另一个进程的内存和资源。
  2. 资源分配 :操作系统会给每个进程分配独立的内存区域、文件描述符等资源。
  3. 重量级 :进程的创建和销毁开销相对较大,因为需要进行内存分配和上下文切换。
  4. 通信方式 :进程间通信(IPC)要借助管道、消息队列、共享内存等机制来实现。

在 Java 中,可以通过ProcessBuilder或者Runtime.getRuntime().exec()方法来创建和控制外部进程。以下是一个简单的示例:

复制代码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class ProcessExample {
    public static void main(String[] args) {
        try {
            // 创建一个进程来执行系统命令(以Windows的ipconfig为例)
            Process process = Runtime.getRuntime().exec("ipconfig");
            
            // 获取进程的输出流并读取
            BufferedReader reader = new BufferedReader(
                new InputStreamReader(process.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
            
            // 等待进程执行完毕并获取返回值
            int exitCode = process.waitFor();
            System.out.println("进程退出码: " + exitCode);
            
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

1.1.2 线程(Thread)

线程是进程中的一个执行单元,也被称作轻量级进程。一个进程可以包含多个线程,这些线程共享进程的内存空间和系统资源,但各自拥有独立的程序计数器、栈空间和局部变量。线程的主要特点如下:

  1. 共享资源 :同一进程内的线程可以共享堆内存、静态变量等资源。
  2. 轻量级 :线程的创建和切换开销较小,因为不需要重新分配内存和系统资源。
  3. 并发执行 :在多核 CPU 环境下,多个线程能够实现真正的并行执行。
  4. 同步问题 :由于线程共享资源,所以需要通过同步机制(如synchronized、Lock)来避免数据竞争和不一致的问题。

在 Java 中,创建线程主要有两种方式:

  1. 继承 Thread 类

    public class MyThread extends Thread {
    @Override
    public void run() {
    System.out.println("线程执行中: " + Thread.currentThread().getName());
    }

    复制代码
     public static void main(String[] args) {
         MyThread thread = new MyThread();
         thread.start(); // 启动线程
         System.out.println("主线程执行中: " + Thread.currentThread().getName());
     }

    }

  2. 实现 Runnable 接口

    public class MyRunnable implements Runnable {
    @Override
    public void run() {
    System.out.println("线程执行中: " + Thread.currentThread().getName());
    }

    复制代码
     public static void main(String[] args) {
         Thread thread = new Thread(new MyRunnable());
         thread.start(); // 启动线程
         System.out.println("主线程执行中: " + Thread.currentThread().getName());
     }

    }

1.1.3 进程与线程的区别

|----------|-----------------------|----------------------|
| 比较维度 | 进程 | 线程 |
| 资源占用 | 拥有独立的内存和系统资源 | 共享进程资源,仅拥有自己的栈和程序计数器 |
| 调度 | 进程是操作系统进行资源分配和调度的基本单位 | 线程是 CPU 调度和分派的基本单位 |
| 并发性 | 不同进程之间可以并发执行 | 同一进程内的多个线程可以并发执行 |
| 通信方式 | 进程间通信(IPC),如管道、消息队列等 | 直接共享内存,通过同步机制通信 |
| 上下文切换开销 | 开销大 | 开销小 |
| 创建和销毁开销 | 开销大 | 开销小 |

1.1.4 线程的生命周期

Java 线程的生命周期包含以下几种状态:

  1. 新建(New) :线程对象被创建,但还没有调用start()方法。
  2. 就绪(Runnable) :线程已经启动,正在等待 CPU 时间片。
  3. 运行(Running) :线程获得 CPU 执行权,正在执行run()方法中的代码。
  4. 阻塞(Blocked) :线程因为等待锁、IO 操作等原因暂时停止执行。
  5. 等待(Waiting) :线程调用了wait()、join()等方法,进入无限期等待状态。
  6. 超时等待(Timed Waiting) :线程调用了wait(long)、sleep(long)等方法,在指定时间后会自动恢复。
  7. 终止(Terminated) :线程的run()方法执行完毕或者因为异常退出。

1.2 并发和并行

1.2.1 并发(Concurrency)

并发是指在同一时间段内 ,系统能够处理多个任务的能力。这些任务在宏观上看似是同时执行的,但在微观层面,它们可能是交替执行的。并发的核心在于任务的切换和调度 ,通过快速切换执行上下文,让用户感觉多个任务在同时进行。

特点

  • 多个任务在逻辑上同时执行,但物理上可能是串行的。
  • 适用于 I/O 密集型场景(如网络请求、文件读写),因为任务在等待 I/O 时可以让出 CPU 资源。
  • 通过线程、协程等轻量级执行单元实现。

示例场景

  • 浏览器同时处理多个标签页的加载。
  • 服务器同时响应多个客户端请求。

Java 实现

复制代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ConcurrencyExample {
    public static void main(String[] args) {
        // 创建一个固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(2);
        
        // 提交两个任务
        executor.submit(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println("任务1: " + i);
                try { Thread.sleep(100); } catch (InterruptedException e) {}
            }
        });
        
        executor.submit(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println("任务2: " + i);
                try { Thread.sleep(100); } catch (InterruptedException e) {}
            }
        });
        
        executor.shutdown();
    }
}

输出可能是任务 1 和任务 2 交替执行,体现了并发的特性。

1.2.2 并行(Parallelism)

并行是指在同一时刻 ,系统能够真正同时执行多个任务的能力。并行需要依赖多核 CPU 等硬件资源,每个任务分配到独立的 CPU 核心上执行,不存在上下文切换的开销。

特点

  • 多个任务在物理上同时执行,需要多核 CPU 支持。
  • 适用于 CPU 密集型场景(如科学计算、图像处理),可以充分利用多核资源加速计算。
  • 通过多进程、多线程或 GPU 等方式实现。

示例场景

  • 视频渲染时多个线程同时处理不同帧。
  • 数据库并行查询多个分片数据。

Java 实现

复制代码
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

// 计算斐波那契数列的并行实现
class Fibonacci extends RecursiveTask<Integer> {
    final int n;
    Fibonacci(int n) { this.n = n; }

    @Override
    protected Integer compute() {
        if (n <= 1)
            return n;
        Fibonacci f1 = new Fibonacci(n - 1);
        f1.fork();
        Fibonacci f2 = new Fibonacci(n - 2);
        return f2.compute() + f1.join();
    }
}

public class ParallelismExample {
    public static void main(String[] args) {
        ForkJoinPool pool = ForkJoinPool.commonPool();
        int result = pool.invoke(new Fibonacci(10));
        System.out.println("斐波那契数列第10项: " + result);
    }
}

通过 Fork/Join 框架将任务分解为多个子任务并行执行。

1.2.3 并发与并行的区别

|----------|----------------------|---------------------|
| 比较维度 | 并发(Concurrency) | 并行(Parallelism) |
| 核心思想 | 处理多个任务的能力(任务切换) | 同时执行多个任务的能力(物理并行) |
| 时间维度 | 宏观同时,微观交替 | 真正的同时执行 |
| 硬件依赖 | 单核或多核 CPU 均可 | 必须依赖多核 CPU |
| 应用场景 | I/O 密集型任务(如 Web 服务器) | CPU 密集型任务(如科学计算) |
| 实现方式 | 线程、协程、事件循环 | 多进程、多线程、GPU 计算 |
| 目的 | 提高资源利用率,增强系统响应性 | 加速计算,提升吞吐量 |

1.2.4 Java 中的并发与并行工具

  1. 线程池(ThreadPool)

    ExecutorService executor = Executors.newFixedThreadPool(10);

  2. 并行流(Parallel Stream)

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    int sum = numbers.parallelStream()
    .mapToInt(Integer::intValue)
    .sum();

  3. CompletableFuture

    CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "结果1");
    CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "结果2");

    CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2);
    allFutures.thenRun(() -> {
    System.out.println(future1.join() + " " + future2.join());
    });

  4. Fork/Join 框架

    ForkJoinPool pool = new ForkJoinPool();
    RecursiveTask<Integer> task = new MyRecursiveTask(0, 1000);
    int result = pool.invoke(task);

1.2.5 总结

  • 并发 是一种编程模型,通过任务切换提高资源利用率,适合处理多任务的场景。
  • 并行 是一种物理实现,依赖多核硬件同时执行多个任务,适合加速计算。
  • 现代系统通常同时使用并发和并行:
  • 服务器通过并发处理大量客户端请求(如 Web 服务器)。
  • 计算密集型任务通过并行加速(如图像处理、机器学习)。

1.3 同步和异步

1.3.1 同步(Synchronous)

同步是指代码按照顺序依次执行 的模式,每个操作必须等待前一个操作完成后才能开始。在同步编程中,调用者会阻塞 直到被调用的操作执行完毕并返回结果。

特点

  • 代码执行顺序明确,易于理解和调试。
  • 存在阻塞现象,可能导致程序在等待 I/O 时浪费 CPU 资源。
  • 适用于逻辑简单、依赖强的场景。

示例场景

  • 读取文件后立即处理内容。
  • 发送 HTTP 请求后等待响应再继续执行。

Java 同步代码示例

复制代码
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class SynchronousExample {
    public static void main(String[] args) {
        try {
            // 同步发送HTTP请求
            URL url = new URL("https://example.com");
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            
            // 阻塞直到响应返回
            try (BufferedReader reader = new BufferedReader(
                    new InputStreamReader(connection.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                }
            }
            
            // 响应处理完成后继续执行
            System.out.println("请求处理完毕,继续执行后续代码");
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

代码会阻塞在getInputStream()直到服务器返回响应。

1.3.2 异步(Asynchronous)

异步是指代码执行时不需要等待当前操作完成,而是可以继续执行后续代码 ,被调用的操作会在后台完成后通过回调、事件或 Future 等方式通知调用者。

特点

  • 非阻塞执行,提高程序的并发能力和响应速度。
  • 代码逻辑分散,可能增加理解和调试难度。
  • 适用于 I/O 密集型、耗时操作的场景。

示例场景

  • 浏览器异步加载图片,同时不影响页面渲染。
  • 服务器异步处理请求,提高吞吐量。

Java 异步代码示例(使用 CompletableFuture)

复制代码
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class AsynchronousExample {
    public static void main(String[] args) {
        // 异步执行任务
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                // 模拟耗时操作
                Thread.sleep(2000);
                return "异步操作结果";
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        
        // 主线程继续执行
        System.out.println("主线程继续执行其他任务");
        
        // 注册回调函数
        future.thenAccept(result -> {
            System.out.println("异步操作完成,结果: " + result);
        });
        
        // 可选:阻塞等待结果(如果需要)
        try {
            String result = future.get();
            System.out.println("获取到结果: " + result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

主线程不会阻塞,可以继续执行其他任务,异步操作完成后通过thenAccept回调通知。

1.3.3 同步与异步的区别

|-----------|--------------------------|--------------------------|
| 比较维度 | 同步(Synchronous) | 异步(Asynchronous) |
| 执行模式 | 按顺序依次执行,前一个操作完成后才开始下一个 | 不等待当前操作完成,继续执行后续代码 |
| 阻塞特性 | 调用者会被阻塞直到操作完成 | 调用者不会被阻塞 |
| 响应性 | 可能因等待耗时操作而降低整体响应性 | 提高系统并发能力和响应速度 |
| 代码复杂度 | 逻辑简单,易于理解和调试 | 逻辑分散(回调、Future 等),调试难度较高 |
| 适用场景 | 逻辑简单、依赖强的操作 | I/O 密集型、耗时操作的场景 |
| 资源利用 | 等待期间 CPU 资源浪费(尤其 I/O 操作) | 等待期间 CPU 可处理其他任务 |

1.3.4 异步编程模式

常见的异步编程模式包括:

  1. 回调函数(Callback)
  • 优点:简单直接。

  • 缺点:嵌套过深会导致 "回调地狱"(Callback Hell)。

    asyncOperation(param, (result, error) -> {
    if (error != null) {
    // 处理错误
    } else {
    // 处理结果
    }
    });

  1. Future/Promise
  • 优点:避免回调地狱,提供更优雅的结果获取方式。

  • 缺点:仍需显式处理阻塞(如调用get())。

    Future<String> future = executor.submit(() -> {
    // 执行异步操作
    return "结果";
    });

    // 稍后获取结果(可能阻塞)
    String result = future.get();

  1. CompletableFuture (Java 8+):
  • 优点:支持链式调用、组合多个 Future、异常处理。
  • 示例见前文。
  1. 协程(Coroutine)
  • 优点:轻量级,避免线程切换开销,代码结构接近同步。

  • 示例(Java 19 + 虚拟线程):

    Thread.startVirtualThread(() -> {
    // 异步执行代码
    String result = performIO();
    System.out.println("结果: " + result);
    });

1.3.5 同步与异步的应用场景

  1. 同步适用场景
  • 操作之间存在强依赖关系(如数据库事务)。
  • 资源竞争严重,需要严格顺序执行。
  • 简单业务逻辑,无需高并发。
  1. 异步适用场景
  • I/O 密集型操作(如网络请求、文件读写)。
  • 耗时计算(如大数据处理、AI 推理)。
  • 需要高吞吐量的场景(如 Web 服务器、消息队列)。
  • 用户界面响应性要求高的场景(如前端交互、移动应用)。

1.3.4 总结

  • 同步 是代码按顺序执行的模式,简单直观但可能导致阻塞。
  • 异步 是非阻塞执行模式,通过回调、Future 等机制提高并发和响应性。
  • 现代应用通常结合使用同步和异步:
  • 核心业务逻辑使用同步保证正确性和可维护性。
  • I/O 密集型操作使用异步提高性能。

2 线程创建方式

2.1 继承 Thread 类

实现步骤

  1. 创建一个类,继承自Thread类。
  2. 重写run()方法,在这个方法里定义线程要执行的任务。
  3. 创建该类的实例,然后调用start()方法启动线程。

示例代码

复制代码
public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " 执行: " + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

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

特点

  • 编码较为简单,直接继承Thread类就行。
  • 由于 Java 是单继承,继承了Thread类后就不能再继承其他类,这会限制代码的扩展性。
  • 线程和任务是耦合在一起的,线程的逻辑被封装在Thread子类中。

2.2 实现 Runnable 接口

实现步骤

  1. 创建一个类,实现Runnable接口。
  2. 实现run()方法,在其中定义任务逻辑。
  3. 创建Runnable实现类的实例,将其作为参数传递给Thread类的构造函数。
  4. 调用Thread实例的start()方法启动线程。

示例代码

复制代码
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " 执行: " + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        MyRunnable task = new MyRunnable();
        Thread thread1 = new Thread(task, "线程1");
        Thread thread2 = new Thread(task, "线程2");
        
        thread1.start();
        thread2.start();
    }
}

特点

  • 避免了单继承的限制,实现类还能继承其他类并实现多个接口。
  • 任务和线程是分离的,符合面向对象的设计原则,提高了代码的可复用性。
  • 推荐使用这种方式,因为它更灵活,能更好地体现 "数据与逻辑分离" 的思想。

2.3 实现 Callable 接口

实现步骤

  1. 创建一个类,实现Callable<V>接口,这里的V是返回值的类型。
  2. 实现call()方法,该方法可以有返回值,并且能够抛出异常。
  3. 创建Callable实现类的实例,把它包装到FutureTask中。
  4. 将FutureTask实例作为参数传递给Thread类的构造函数。
  5. 调用start()方法启动线程,通过FutureTask的get()方法获取返回值。

示例代码

复制代码
import java.util.concurrent.*;

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

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable task = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask<>(task);
        Thread thread = new Thread(futureTask);
        
        thread.start();
        
        // 获取任务的返回值(会阻塞,直到任务完成)
        Integer result = futureTask.get();
        System.out.println("计算结果: " + result);
    }
}

特点

  • call()方法支持返回值,还能抛出受检查异常。
  • 通过FutureTask可以获取异步计算的结果,在需要处理返回值的场景中很有用。
  • 适用于需要返回执行结果的异步任务。

2.4 使用 CompletableFuture(Java 8+)

实现步骤

  1. 通过CompletableFuture的静态方法创建异步任务。
  2. 可以使用thenApply()、thenAccept()、thenCompose()等方法进行链式操作。
  3. 可以使用join()或get()方法获取结果。

示例代码

复制代码
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class CompletableFutureExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建异步任务
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "异步操作结果";
        });
        
        // 链式操作:转换结果
        CompletableFuture<String> processedFuture = future.thenApply(result -> {
            return result.toUpperCase();
        });
        
        // 异步回调:结果处理完成后执行
        processedFuture.thenAccept(finalResult -> {
            System.out.println("最终结果: " + finalResult);
        });
        
        // 阻塞获取结果(如果需要)
        System.out.println("等待结果...");
        System.out.println(processedFuture.get());
    }
}

特点

  • 提供了函数式编程风格,支持链式调用,代码更加简洁。
  • 内置了线程池管理,默认使用ForkJoinPool.commonPool()。
  • 支持组合多个异步任务,如allOf()、anyOf()等。
  • 适合复杂的异步流程编排,如并行执行多个任务然后合并结果。

2.5 使用虚拟线程(Virtual Threads,Java 19 +)

实现步骤

  1. 创建Runnable或Callable任务。
  2. 通过Thread.startVirtualThread()方法启动虚拟线程。
  3. 虚拟线程会在平台线程上运行,由 JVM 自动管理。

示例代码

复制代码
public class VirtualThreadExample {
    public static void main(String[] args) throws InterruptedException {
        // 创建大量虚拟线程
        for (int i = 0; i < 1000; i++) {
            final int taskId = i;
            Thread.startVirtualThread(() -> {
                System.out.println("虚拟线程 " + taskId + " 开始执行");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("虚拟线程 " + taskId + " 执行完毕");
            });
        }
        
        // 主线程等待所有虚拟线程完成
        Thread.sleep(5000);
    }
}

特点

  • 虚拟线程是轻量级的线程,由 JVM 管理,创建和销毁的成本极低。
  • 非常适合高并发、I/O 密集型的场景,能显著提高系统吞吐量。
  • 与传统线程 API 兼容,使用方式和普通线程类似。
  • 需要 Java 19 及以上版本,并且启用--enable-preview选项。

七、各种线程创建方式的对比

|-------------------|---------------|--------------|-------------|------------|---------------|
| 创建方式 | 是否有返回值 | 是否支持异常抛出 | 是否支持多继承 | 线程管理难度 | 适用场景 |
| 继承 Thread 类 | 否 | 否 | 否 | 简单 | 简单任务,不考虑扩展性 |
| 实现 Runnable 接口 | 否 | 否 | 是 | 简单 | 任务与线程分离的场景 |
| 实现 Callable 接口 | 是 | 是 | 是 | 中等 | 需要返回值的异步任务 |
| CompletableFuture | 是 | 是 | 是 | 中等 | 复杂异步流程编排 |
| 虚拟线程(Java 19+) | 支持(通过 Future) | 是 | 是 | 简单 | 高并发、I/O 密集型场景 |

3 线程常用方法

3.1 总览

|--------------------------------------------------|-------------------------------------------------------------|
| 方法 | 说明 |
| public void start () | 启动一个新线程,Java虚拟机调用此线程的 run 方法 |
| public void run () | 线程启动后调用该方法 |
| public void setName (String name) | 给当前线程取名字 |
| public void getName () | 获取当前线程的名字 线程存在默认名称:子线程是 Thread-索引,主线程是 main |
| public static Thread currentThread () | 获取当前线程对象,代码在哪个线程中执行 |
| public static void sleep (long time) | 让当前线程休眠多少毫秒再继续执行 Thread.sleep(0) : 让操作系统立刻重新进行一次 CPU 竞争 |
| public static native void yield () | 提示线程调度器让出当前线程对 CPU 的使用 |
| public final int getPriority () | 返回此线程的优先级 |
| public final void setPriority (int priority) | 更改此线程的优先级,常用 1 5 10 |
| public void interrupt () | 中断这个线程,异常处理机制 |
| public static boolean interrupted () | 判断当前线程是否被打断,清除打断标记 |
| public boolean isInterrupted () | 判断当前线程是否被打断,不清除打断标记 |
| public final void join () | 等待这个线程结束 |
| public final void join (long millis) | 等待这个线程死亡 millis 毫秒,0 意味着永远等待 |
| public final native boolean isAlive () | 线程是否存活(还没有运行完毕) |
| public final void setDaemon (boolean on) | 将此线程标记为守护线程或用户线程 |

3.2 线程创建与启动

1. public void start()

  • 作用 :启动线程,使线程进入就绪状态(等待 CPU 调度),并自动调用run()方法。
  • 注意
  • 每个线程只能调用一次start(),重复调用会抛出IllegalThreadStateException。
  • 不要直接调用 **run()**方法 ,否则只是普通的方法调用,不会创建新线程。

示例

复制代码
Thread t = new Thread(() -> System.out.println("线程执行"));
t.start(); // 正确:启动新线程
// t.start(); // 错误:重复调用

3.3 线程状态控制

1. public static void sleep(long millis)

  • 作用 :让当前线程暂停执行指定时间(毫秒),进入TIMED_WAITING 状态。
  • 特点
  • 不释放锁(若持有锁)。
  • 可被中断(interrupt()),抛出InterruptedException。

示例

复制代码
try {
    Thread.sleep(1000); // 暂停1秒
} catch (InterruptedException e) {
    e.printStackTrace();
}

2. public static void yield()

  • 作用 :当前线程让出 CPU 时间片,进入RUNNABLE 状态,允许其他线程执行。
  • 特点
  • 仅为建议,操作系统可能忽略。
  • 不释放锁。
  • 与sleep(0)类似,但语义更明确。

3. public final void join() / join(long millis)

  • 作用 :等待当前线程终止(或指定时间)。常用于主线程等待子线程完成。

  • 示例

    Thread t = new Thread(() -> {
    // 子线程任务
    });
    t.start();
    t.join(); // 主线程阻塞,直到t执行完毕

3.4 线程中断

1. public void interrupt()

  • 作用 :中断线程(设置中断标志位)。
  • 若线程处于sleep()、wait()、join()等阻塞状态,会抛出InterruptedException并清除标志位。
  • 若线程正常运行,仅设置标志位,需通过isInterrupted()检测。

2. public boolean isInterrupted()

  • 作用 :检测线程是否被中断(不清除标志位)。

3. public static boolean interrupted()

  • 作用 :检测当前线程是否被中断,并清除中断标志位

正确处理中断的示例

复制代码
Thread t = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        try {
            // 执行任务
            Thread.sleep(100);
        } catch (InterruptedException e) {
            // 重置中断标志位,以便退出循环
            Thread.currentThread().interrupt();
            break;
        }
    }
});
t.start();
t.interrupt(); // 中断线程

3.5 线程优先级

1. public final void setPriority(int newPriority)

  • 作用 :设置线程优先级(1-10),默认 5。
  • 注意
  • 优先级高的线程理论上更可能被 CPU 调度,但依赖操作系统实现(如 Windows 和 Linux 对优先级的处理不同)。
  • 不建议过度依赖优先级控制线程执行顺序。

示例

复制代码
Thread t = new Thread();
t.setPriority(Thread.MAX_PRIORITY); // 10
t.setPriority(Thread.MIN_PRIORITY); // 1
t.setPriority(Thread.NORM_PRIORITY); // 5(默认)

3.6 线程状态查询

1. public Thread.State getState()

  • 作用 :获取线程的当前状态(枚举类型Thread.State),包括:
  • NEW:新建(未调用start())。
  • RUNNABLE:就绪或运行中。
  • BLOCKED:等待锁。
  • WAITING:无限期等待(如wait()、join())。
  • TIMED_WAITING:限时等待(如sleep(1000))。
  • TERMINATED:已终止。

示例

复制代码
Thread t = new Thread();
System.out.println(t.getState()); // NEW

t.start();
System.out.println(t.getState()); // RUNNABLE 或 TIMED_WAITING

3.7 线程同步与通信

1. public final void wait() / wait(long timeout)

  • 作用 :当前线程释放对象锁,进入WAITINGTIMED_WAITING 状态,直到其他线程调用该对象的notify()或notifyAll()。
  • 注意 :必须在synchronized块中调用,否则抛出IllegalMonitorStateException。

2. public final void notify() / notifyAll()

  • 作用 :唤醒在该对象上等待的线程(notify()随机唤醒一个,notifyAll()唤醒所有)。
  • 注意 :必须在synchronized块中调用。

生产者 - 消费者示例

复制代码
class SharedResource {
    private int data;
    private boolean available = false;

    public synchronized void produce(int value) {
        while (available) {
            try {
                wait(); // 等待消费者取走数据
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        data = value;
        available = true;
        notifyAll(); // 通知消费者数据已就绪
    }

    public synchronized int consume() {
        while (!available) {
            try {
                wait(); // 等待生产者生产数据
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        available = false;
        notifyAll(); // 通知生产者数据已取走
        return data;
    }
}

3.8 守护线程

1. public final void setDaemon(boolean on)

  • 作用 :设置线程为守护线程(true)或用户线程(false)。
  • 特点
  • 守护线程在所有用户线程结束后自动终止(如垃圾回收线程)。
  • 必须在start()前调用,否则抛出IllegalThreadStateException。

示例

复制代码
Thread daemonThread = new Thread(() -> {
    while (true) {
        System.out.println("守护线程运行中...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});
daemonThread.setDaemon(true);
daemonThread.start();

// 主线程结束后,守护线程自动终止

3.9 其他常用方法

1. public final String getName() / setName(String name)

  • 作用 :获取 / 设置线程名称,用于调试和日志记录。

2. public final boolean isAlive()

  • 作用 :判断线程是否处于活动状态(已启动且未终止)。

3. public static Thread currentThread()

  • 作用 :获取当前执行的线程实例。

3.10 方法对比与注意事项

|-------------|----------|---------|----------|------------|
| 方法 | 是否静态 | 释放锁 | 中断响应 | 用途场景 |
| sleep() | 是 | 否 | 是 | 暂停执行 |
| yield() | 是 | 否 | 否 | 让出 CPU 时间片 |
| join() | 否 | 否 | 是 | 等待其他线程结束 |
| wait() | 否 | 是 | 是 | 线程间通信 |
| notify() | 否 | 否 | 否 | 唤醒等待线程 |
| interrupt() | 否 | 否 | 否 | 中断线程 |

4 线程关闭方式

4.1 线程关闭的错误方式

1. public final void stop()(已弃用)

  • 问题 :强制终止线程,立即释放所有锁,可能导致数据不一致(如对象处于半初始化状态)。

  • 示例java

    Thread t = new Thread(() -> {
    synchronized (lock) {
    // 修改共享资源
    // stop()可能在此处被调用,导致锁释放,资源未完全修改
    }
    });
    t.start();
    t.stop(); // 危险!

2. System.exit(int status)

  • 问题 :终止整个 JVM,所有线程(包括守护线程)都会被强制终止,未释放的资源(如文件句柄)无法清理。

4.2 线程优雅关闭的正确方式

4.2.1 通过标志位控制(推荐)

  • 原理 :在线程内部设置一个volatile 标志位 ,外部线程通过修改标志位通知目标线程停止。
  • 优点 :安全可控,适合长时间运行的任务。

示例代码

复制代码
public class FlagControlledThread extends Thread {
    private volatile boolean running = true; // 必须为volatile,保证可见性

    @Override
    public void run() {
        while (running) {
            try {
                // 执行任务
                Thread.sleep(100);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // 恢复中断状态
                break;
            }
        }
        // 清理资源(如关闭文件、释放连接等)
        System.out.println("线程正常退出");
    }

    public void shutdown() {
        running = false; // 设置标志位,通知线程停止
    }
}

// 使用示例
public static void main(String[] args) throws InterruptedException {
    FlagControlledThread thread = new FlagControlledThread();
    thread.start();
    
    // 主线程休眠一段时间后请求线程关闭
    Thread.sleep(2000);
    thread.shutdown(); // 优雅关闭
    thread.join();     // 等待线程终止
}

2. 使用中断机制(推荐)

  • 原理 :通过interrupt()设置中断标志,线程在合适的时机检查标志并退出。
  • 适用场景 :线程处于阻塞状态(如sleep()、wait()、join())时也能及时响应。

示例代码

复制代码
public class InterruptibleThread extends Thread {
    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) {
            try {
                // 执行任务
                Thread.sleep(100); // 可能被中断,抛出InterruptedException
            } catch (InterruptedException e) {
                // 1. 恢复中断状态
                Thread.currentThread().interrupt();
                // 2. 退出循环
                break;
            }
        }
        // 清理资源
        System.out.println("线程被中断,正常退出");
    }
}

// 使用示例
public static void main(String[] args) throws InterruptedException {
    InterruptibleThread thread = new InterruptibleThread();
    thread.start();
    
    // 主线程休眠后请求中断
    Thread.sleep(2000);
    thread.interrupt(); // 发送中断信号
    thread.join();      // 等待线程终止
}

3. 结合标志位和中断(最佳实践)

  • 原理 :同时使用标志位和中断机制,兼顾灵活性和健壮性。

示例代码

复制代码
public class HybridShutdownThread extends Thread {
    private volatile boolean running = true;

    @Override
    public void run() {
        while (running && !Thread.currentThread().isInterrupted()) {
            try {
                // 执行任务
                Thread.sleep(100);
            } catch (InterruptedException e) {
                running = false; // 中断后设置标志位
                Thread.currentThread().interrupt(); // 恢复中断状态
            }
        }
        // 清理资源
        System.out.println("线程优雅关闭");
    }

    public void shutdown() {
        running = false;       // 设置标志位
        interrupt();           // 中断可能的阻塞操作
    }
}

4.2.2 线程池的优雅关闭

1. ExecutorService.shutdown()

  • 作用 :平缓关闭线程池,不再接受新任务,已提交的任务会继续执行。

2. ExecutorService.shutdownNow()

  • 作用 :强制关闭线程池,尝试中断正在执行的任务,并返回未执行的任务列表。
  • 原理
  1. 标记线程池为停止状态 :拒绝新任务的提交。
  2. 中断所有活跃线程 :遍历线程池中的工作线程,调用Thread.interrupt()。
  3. 清空任务队列 :将等待队列中的任务转移到列表中返回。
  4. 返回未执行的任务 :返回步骤 3 中转移的任务列表。

3. 最佳实践代码

复制代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ThreadPoolShutdownExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(5);
        
        // 提交任务
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            executor.submit(() -> {
                try {
                    System.out.println("执行任务: " + taskId);
                    Thread.sleep(1000);
                    System.out.println("任务 " + taskId + " 完成");
                } catch (InterruptedException e) {
                    System.out.println("任务 " + taskId + " 被中断");
                    Thread.currentThread().interrupt();
                }
            });
        }
        
        // 优雅关闭线程池
        executor.shutdown(); // 停止接受新任务
        
        try {
            // 等待已提交的任务执行完毕(最多等待30秒)
            if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
                // 超时未完成,强制关闭
                executor.shutdownNow();
                
                // 再次等待剩余任务中断(最多等待10秒)
                if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
                    System.err.println("线程池关闭失败");
                }
            }
        } catch (InterruptedException e) {
            // 主线程被中断,强制关闭
            executor.shutdownNow();
            Thread.currentThread().interrupt();
        }
        
        System.out.println("线程池已关闭");
    }
}

4.3 资源清理的最佳实践

在线程关闭时,需确保释放所有持有的资源(如文件、网络连接、数据库连接等),推荐使用:

  1. try-with-resources :自动关闭实现了AutoCloseable接口的资源。

    @Override
    public void run() {
    try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
    // 使用资源
    } catch (IOException e) {
    e.printStackTrace();
    }
    // 资源自动关闭
    }

  2. finally 块 :确保无论是否发生异常,资源都能被关闭。

    @Override
    public void run() {
    Connection conn = null;
    try {
    conn = getDatabaseConnection();
    // 使用连接执行操作
    } catch (SQLException e) {
    e.printStackTrace();
    } finally {
    if (conn != null) {
    try {
    conn.close(); // 手动关闭资源
    } catch (SQLException e) {
    e.printStackTrace();
    }
    }
    }
    }

4.4 总结

线程的优雅关闭需遵循以下原则:

  1. 避免强制终止 :不使用stop()、destroy()等方法。
  2. 协作式关闭 :通过标志位或中断机制通知线程自行终止。
  3. 资源清理 :使用try-with-resources或finally确保资源释放。
  4. 线程池关闭 :按顺序调用shutdown()和awaitTermination()。

通过以上方法,可以确保线程安全退出,避免数据不一致和资源泄漏,提高系统的稳定性和可靠性。

5 线程的状态

5.1 六种状态(Thread.State 枚举)

5.1.1 NEW(新建)

  • 含义 :线程已被创建(通过new Thread()),但尚未调用start()方法。

  • 示例

    Thread t = new Thread(() -> System.out.println("线程执行"));
    // 此时t处于NEW状态

  • 特点

  • 线程对象已创建,但尚未分配系统资源(如操作系统线程)。
  • 只能调用start()方法,其他操作(如interrupt())会抛出异常。

5.1.2 RUNNABLE(可运行)

  • 含义 :线程已启动(调用start()),正在 JVM 中运行或等待 CPU 时间片。
  • 细分状态
  • Running :线程正在 CPU 上执行。
  • Ready :线程处于就绪队列,等待操作系统调度。
  • 示例

    t.start(); // 调用start()后,t进入RUNNABLE状态

  • 特点

  • 线程可能正在执行,也可能在等待 CPU 资源。
  • Java 将操作系统层面的Running和Ready状态统一抽象为RUNNABLE。

5.1.3 BLOCKED(阻塞)

  • 含义 :线程正在等待获取锁(如synchronized块或方法)。
  • 触发场景
  • 线程尝试进入synchronized代码块,但锁已被其他线程持有。
  • 锁被释放后,线程从BLOCKED变为RUNNABLE,重新竞争锁。
  • 示例

    public class BlockedExample {
    private static final Object LOCK = new Object();

    复制代码
      public static void main(String[] args) {
          Thread t1 = new Thread(() -> {
              synchronized (LOCK) {
                  // 持有锁,长时间运行
                  try { Thread.sleep(1000); } catch (InterruptedException e) {}
              }
          });
    
          Thread t2 = new Thread(() -> {
              synchronized (LOCK) { // t2在此处阻塞,进入BLOCKED状态
                  System.out.println("t2获取到锁");
              }
          });
    
          t1.start();
          t2.start();
      }

    }

5.1.4 WAITING(无限期等待)

  • 含义 :线程等待另一个线程执行特定操作,需通过其他线程显式唤醒。
  • 触发方法
  • Object.wait():释放对象锁,进入等待状态,直到其他线程调用notify()/notifyAll()。
  • Thread.join():等待目标线程终止。
  • LockSupport.park():等待许可(permit)。
  • 示例

    public class WaitingExample {
    public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(() -> {
    synchronized (this) {
    try {
    wait(); // 线程t进入WAITING状态
    } catch (InterruptedException e) {}
    }
    });

    复制代码
          t.start();
          Thread.sleep(100);
          System.out.println(t.getState()); // 输出WAITING
    
          synchronized (this) {
              notify(); // 唤醒线程t
          }
      }

    }

5.1.5 TIMED_WAITING(限时等待)

  • 含义 :线程在指定时间内等待,超时后自动恢复。
  • 触发方法
  • Thread.sleep(long millis):线程暂停执行指定时间。
  • Object.wait(long timeout):释放锁,等待指定时间或被唤醒。
  • Thread.join(long millis):等待目标线程终止,最多等待指定时间。
  • LockSupport.parkNanos(long nanos)/parkUntil(long deadline):限时等待许可。
  • 示例

    public class TimedWaitingExample {
    public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(() -> {
    try {
    Thread.sleep(2000); // 线程t进入TIMED_WAITING状态
    } catch (InterruptedException e) {}
    });

    复制代码
          t.start();
          Thread.sleep(100);
          System.out.println(t.getState()); // 输出TIMED_WAITING
      }

    }

5.1.6 TERMINATED(终止)

  • 含义 :线程执行完毕或因异常终止,生命周期结束。
  • 触发条件
  • run()方法正常返回。
  • run()方法抛出未捕获的异常。
  • 示例

    public class TerminatedExample {
    public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(() -> {
    System.out.println("线程执行");
    });

    复制代码
          t.start();
          Thread.sleep(100); // 等待线程t执行完毕
          System.out.println(t.getState()); // 输出TERMINATED
      }

    }