Skip to content

18.3 线程启动

线程启动的概念

线程启动是指将线程从新建状态转换到运行状态的过程。在 Java 中,线程启动是通过调用 start() 方法实现的,而不是直接调用 run() 方法。

线程启动的方法

1. 使用 start() 方法

语法

java
thread.start();

作用

  • 启动线程,使其进入就绪状态
  • 等待 CPU 时间片,获得时间片后开始执行 run() 方法
  • 每个线程只能启动一次,多次调用会抛出 IllegalThreadStateException

示例

java
public class ThreadStartExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 开始执行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 执行完毕");
        }, "工作线程");
        
        // 启动线程
        thread.start();
        
        System.out.println(Thread.currentThread().getName() + " 继续执行");
    }
}

2. 直接调用 run() 方法的问题

症状:线程没有并发执行,而是在当前线程中串行执行

原因:直接调用 run() 方法只是普通的方法调用,不会启动新线程

示例

java
public class DirectRunExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 开始执行");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 执行完毕");
        }, "工作线程");
        
        // 错误:直接调用 run() 方法
        thread.run();
        
        System.out.println(Thread.currentThread().getName() + " 继续执行");
    }
}

输出

main 开始执行
main 执行完毕
main 继续执行

线程启动的原理

1. 线程状态转换

当调用 start() 方法时,线程会经历以下状态转换:

  1. 新建(New):线程对象已创建,但尚未启动
  2. 就绪(Runnable):线程已经启动,等待 CPU 时间片
  3. 运行(Running):线程获得 CPU 时间片,执行 run() 方法
  4. 终止(Terminated):线程执行完毕或异常终止

2. 启动过程

  1. 创建线程对象:通过 new Thread() 创建线程对象
  2. 调用 start() 方法:请求系统创建新线程
  3. 系统分配资源:系统为线程分配栈空间等资源
  4. 线程进入就绪状态:线程等待 CPU 时间片
  5. 执行 run() 方法:线程获得 CPU 时间片后,开始执行 run() 方法
  6. 线程终止run() 方法执行完毕,线程进入终止状态

线程启动的注意事项

1. 只能启动一次

症状:多次调用 start() 方法会抛出 IllegalThreadStateException

解决方案:一个线程只能启动一次,需要创建新的线程对象来执行新的任务

示例

java
// 错误:多次调用 start() 方法
Thread thread = new Thread(() -> {
    System.out.println("线程执行");
});
thread.start();
// thread.start(); // 抛出 IllegalThreadStateException

// 正确:创建新的线程对象
Thread thread1 = new Thread(() -> {
    System.out.println("线程1执行");
});
Thread thread2 = new Thread(() -> {
    System.out.println("线程2执行");
});
thread1.start();
thread2.start();

2. 线程启动的顺序

症状:线程启动的顺序与执行的顺序不一致

原因:线程启动后进入就绪状态,等待 CPU 时间片,执行顺序由操作系统的调度算法决定

示例

java
public class ThreadOrderExample {
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            System.out.println("线程1 开始执行");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程1 执行完毕");
        }, "线程1");
        
        Thread thread2 = new Thread(() -> {
            System.out.println("线程2 开始执行");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程2 执行完毕");
        }, "线程2");
        
        Thread thread3 = new Thread(() -> {
            System.out.println("线程3 开始执行");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程3 执行完毕");
        }, "线程3");
        
        // 启动线程
        thread1.start();
        thread2.start();
        thread3.start();
        
        System.out.println("主线程执行完毕");
    }
}

可能的输出

主线程执行完毕
线程3 开始执行
线程1 开始执行
线程2 开始执行
线程3 执行完毕
线程1 执行完毕
线程2 执行完毕

3. 线程启动的开销

症状:频繁创建和启动线程会导致系统开销增加

解决方案:使用线程池来重用线程,减少线程创建和销毁的开销

示例

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

public class ThreadPoolStartExample {
    public static void main(String[] args) {
        // 创建线程池
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        // 提交多个任务
        for (int i = 1; i <= 10; i++) {
            final int taskId = i;
            executor.submit(() -> {
                System.out.println(Thread.currentThread().getName() + " 执行任务 " + taskId);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
        
        // 关闭线程池
        executor.shutdown();
    }
}

线程启动的最佳实践

  1. 使用 start() 方法:使用 start() 方法启动线程,而不是直接调用 run() 方法

  2. 避免多次启动:一个线程只能启动一次,需要创建新的线程对象来执行新的任务

  3. 使用线程池:对于大量短期任务,使用线程池可以减少线程创建和销毁的开销

  4. 设置线程名称:为线程设置有意义的名称,便于调试和监控

  5. 处理异常:在线程的 run() 方法中捕获并处理异常,避免线程意外终止

  6. 合理设置线程优先级:根据任务的重要性设置线程优先级,但不要过度依赖优先级

  7. 避免线程安全问题:对于共享资源,使用适当的同步机制

  8. 关闭线程池:使用完毕后关闭线程池,避免资源泄漏

示例:线程启动的综合应用

示例 1:多线程下载文件

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

public class FileDownloadExample {
    public static void main(String[] args) {
        // 创建线程池
        ExecutorService executor = Executors.newFixedThreadPool(3);
        
        // 模拟下载任务
        String[] files = {"file1.txt", "file2.txt", "file3.txt", "file4.txt", "file5.txt"};
        
        for (String file : files) {
            final String fileName = file;
            executor.submit(() -> {
                System.out.println(Thread.currentThread().getName() + " 开始下载 " + fileName);
                try {
                    // 模拟下载时间
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " 完成下载 " + fileName);
            });
        }
        
        // 关闭线程池
        executor.shutdown();
    }
}

示例 2:多线程处理数据

java
public class DataProcessingExample {
    public static void main(String[] args) {
        // 创建任务
        Runnable task1 = () -> {
            System.out.println("任务1 开始处理数据");
            processData("任务1");
            System.out.println("任务1 完成处理数据");
        };
        
        Runnable task2 = () -> {
            System.out.println("任务2 开始处理数据");
            processData("任务2");
            System.out.println("任务2 完成处理数据");
        };
        
        Runnable task3 = () -> {
            System.out.println("任务3 开始处理数据");
            processData("任务3");
            System.out.println("任务3 完成处理数据");
        };
        
        // 创建并启动线程
        Thread thread1 = new Thread(task1, "线程1");
        Thread thread2 = new Thread(task2, "线程2");
        Thread thread3 = new Thread(task3, "线程3");
        
        thread1.start();
        thread2.start();
        thread3.start();
    }
    
    private static void processData(String taskName) {
        try {
            // 模拟数据处理时间
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

总结

线程启动是 Java 并发编程的重要环节:

  1. 线程启动的方法

    • 使用 start() 方法启动线程
    • 避免直接调用 run() 方法
  2. 线程启动的原理

    • 线程状态从新建转换到就绪
    • 等待 CPU 时间片
    • 获得时间片后执行 run() 方法
  3. 线程启动的注意事项

    • 只能启动一次
    • 启动顺序与执行顺序不一致
    • 频繁启动线程会增加系统开销
  4. 线程启动的最佳实践

    • 使用 start() 方法
    • 避免多次启动
    • 使用线程池
    • 设置线程名称
    • 处理异常
    • 避免线程安全问题

通过合理启动线程,可以有效地实现并发编程,提高程序的性能和响应速度。

© 2026 编程马·菜鸟教程 版权所有