多线程与并发-理论基础

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. 同步问题:由于线程共享资源,所以需要通过同步机制(如synchronizedLock)来避免数据竞争和不一致的问题。

在 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());
    }
}
  1. 实现 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);
  1. 并行流(Parallel Stream)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.parallelStream()
                .mapToInt(Integer::intValue)
                .sum();
  1. 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());
});
  1. 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()方法启动线程,通过FutureTaskget()方法获取返回值。

示例代码

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. 创建RunnableCallable任务。
  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();
    }
    // 资源自动关闭
}
  1. 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-resourcesfinally确保资源释放。
  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 将操作系统层面的RunningReady状态统一抽象为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
    }
}
From:https://www.cnblogs.com/icutey/p/18889195
褐瞳cutey
100+评论
captcha