Skip to content

01 多线程初阶:解谜多线程世界

在日常开发中,你是否曾遇到这样的情景:应用程序需要执行多个任务,但你希望它们能够同时运行,以提高性能和响应性。这正是多线程编程的核心价值所在。

在本文中,我们将学习 Java 多线程编程的基础知识,包括线程的创建、使用、生命周期以及线程安全产生的原因。

一、线程创建与启动

线程是轻量级的,共享相同的进程内存空间。在 Java 中,线程可以使用 java.lang.Thread 类来创建和管理。

1. 实现 Runnable 接口

java
public class ThreadRunnableTest {
    public static void main(String[] args) {
        Thread thread = new Thread(new Task());
        thread.setName("测试线程");
        thread.start();
    }
    static class Task implements Runnable {
        @Override
        public void run() {
            System.out.println("线程运行,线程名称为:" + Thread.currentThread().getName());
        }
    }
}

调用 start 方法后,主线程会开启一条子线程去执行任务,主线程继续向下执行,两者并行执行。

2. 实现 Callable 接口(有返回值)

java
public class ThreadCallableTest {
    public static void main(String[] args) throws Exception {
        FutureTask<String> stringFutureTask = new FutureTask<>(new TaskReturn());
        Thread thread = new Thread(stringFutureTask);
        thread.setName("测试线程");
        thread.start();
        System.out.println(stringFutureTask.get());
    }
    private static class TaskReturn implements Callable<String> {
        @Override
        public String call() {
            return String.format("我被线程【%s】执行了", Thread.currentThread().getName());
        }
    }
}

Callable 无法直接传递到 Thread 中,需要使用 FutureTask 来包装。FutureTaskget 方法可以获取异步任务的执行结果。

主线程与子线程:点击运行后,执行 main 方法的线程称为主线程,从 main 方法中创建的线程称为子线程

二、线程的主要参数与 API

1. 优先级

线程的优先级是一个整数值(1-10),1 表示最低优先级,10 表示最高优先级。

java
thread.setPriority(Thread.MAX_PRIORITY); // 设置为最高优先级

注意:线程优先级依赖于底层操作系统支持,不保证严格按照优先级顺序执行,且过度依赖可能导致不可预测的结果。

常见问题

  • 优先级反转:低优先级线程持有锁后,高优先级线程等待时被阻塞
  • 饥饿:高优先级线程长期占用资源,低优先级线程无法获得执行机会

2. 线程名称

java
thread.setName("测试线程");           // 设置线程名称
Thread.currentThread().getName()      // 获取当前线程名称

线程名称在排查问题(如死锁、系统变慢)时非常重要,可以使用 jconsolejstack 等 JVM 工具监控。

3. 守护线程

守护线程的生命周期随主线程结束而终止,用于执行后台任务(如垃圾回收、定时任务、监控等)。

java
Thread thread = new Thread(new Task());
thread.setDaemon(true);  // 设置为守护线程
thread.start();

守护线程 vs 工作线程

  • 守护线程:主线程执行完毕,守护线程立即停止
  • 工作线程:JVM 会等待所有工作线程结束后才停止

注意:守护线程可能来不及执行资源回收(如关闭 JDBC 连接),需谨慎使用。

4. 停止线程

不推荐使用 thread.stop(),会导致资源不释放。

推荐使用 interrupt 方法请求线程停止:

java
public class StopThreadTest {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Task());
        thread.setName("测试线程");
        thread.start();
        TimeUnit.SECONDS.sleep(1);
        thread.interrupt();  // 发出停止信号
    }
    private static class Task implements Runnable {
        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("我执行了");
            }
        }
    }
}

interrupt 方法会在线程等待条件时触发 InterruptedException 异常,需配合循环检测中断信号来终止线程。

三、线程生命周期和状态

Java 线程的生命周期包括以下状态:

状态说明
New线程对象已创建,但尚未启动
Runnable线程已准备好运行,等待 CPU 时间片
Running线程获得 CPU 时间片并开始执行
Blocked线程被阻塞,等待锁或其他条件
Waiting线程等待其他线程通知继续执行
Timed Waiting线程等待一段时间后自动唤醒
Terminated线程生命周期结束

状态转换图

四、竞态条件和临界区

并发安全问题的产生源于多线程同时访问共享资源,且操作顺序敏感。

小故事:面包店的小明雇佣了两个助手小红和小绿做蛋糕。第三步"添加奶油和装饰"需要按顺序完成,但由于两人同时操作,导致蛋糕做砸了。

解决方案:引入规则——同时只有一人能操作最后一步。这个规则就是

关键概念

  • 临界区:共享资源或代码段,多线程同时访问会产生问题
  • 竞态条件:多个线程同时以不同顺序访问临界区
  • :保证同一时刻只有一个线程能访问临界区

五、总结

线程是提升服务器资源利用率的关键工具。在本节中,我们学习了:

  • 线程的创建方式(RunnableCallable
  • 线程的参数(优先级、名称、守护线程)
  • 线程的停止方法(interrupt
  • 线程的生命周期和状态
  • 并发安全问题的产生原因(竞态条件、临界区)

下一节,我们将学习如何合理、高效、安全地使用线程。