Java线程创建全解析:从基础到实战

概述

多线程是Java并发编程的核心,合理创建和管理线程能显著提升程序性能与响应速度。本文将系统性地介绍Java中创建线程的多种方式:继承Thread类、实现Runnable接口、实现Callable接口配合FutureTask、使用线程池(ExecutorService),以及Lambda简化写法。同时涵盖线程生命周期、优先级、守护线程等基础概念,并通过实战示例演示如何在实际项目中应用。最后总结常见陷阱与最佳实践,帮助读者从理论到实战全面掌握Java线程创建。

核心概念

1. 线程与进程

  • 进程:操作系统分配资源的基本单位,拥有独立的内存空间。
  • 线程:CPU调度的最小单位,共享进程的资源(堆、方法区),每个线程拥有独立的程序计数器、栈和局部变量。

2. 线程的生命周期

Java线程生命周期包含6种状态(Thread.State枚举):

  • NEW:刚创建,尚未启动。
  • RUNNABLE:可运行(包含就绪和运行中)。
  • BLOCKED:阻塞等待监视器锁。
  • WAITING:等待其他线程显式唤醒。
  • TIMED_WAITING:超时等待。
  • TERMINATED:执行完毕。

3. 创建线程的四种核心方式

方式 特点 返回值 异常处理 适用场景
继承Thread 简单,但受限于单继承 线程内处理 简单任务,不推荐
实现Runnable 无返回值,可避免单继承限制 线程内处理 大多数场景
实现Callable + FutureTask 有返回值,可抛出异常 通过Future.get()捕获 需要计算结果的异步任务
线程池ExecutorService 管理线程生命周期,复用线程 有/无 取决于提交的任务 高并发、大量短任务

4. 守护线程(Daemon Thread)

  • 默认用户线程(User Thread),t.setDaemon(true)设为守护线程。
  • 当所有用户线程结束,JVM强制退出守护线程。
  • 常用于后台监控、GC等。

5. 线程优先级

  • Thread.setPriority(1~10),默认5。
  • 优先级依赖操作系统,不保证执行顺序。

实战:五种创建方式代码详解

方式一:继承Thread类

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

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

缺点:Java单继承,如果类已经继承其他类则无法使用。

方式二:实现Runnable接口

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

    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.start();
    }
}

优点:解耦任务与执行,可共享一个Runnable实例给多个线程。

方式三:实现Callable接口 + FutureTask

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

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

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask futureTask = new FutureTask(new MyCallable());
        Thread t = new Thread(futureTask);
        t.start();
        Integer result = futureTask.get(); // 阻塞等待结果
        System.out.println("结果为:" + result);
    }
}

说明FutureTask同时实现了RunnableFuture,可提交给线程执行并获取返回值。

方式四:使用线程池(ExecutorService)

import java.util.concurrent.*;

public class ThreadPoolDemo {
    public static void main(String[] args) {
        // 创建固定大小线程池
        ExecutorService executor = Executors.newFixedThreadPool(4);

        // 提交Runnable任务
        executor.execute(() -> {
            System.out.println(Thread.currentThread().getName() + " 执行任务");
        });

        // 提交Callable任务并获取Future
        Future future = executor.submit(() -> {
            TimeUnit.SECONDS.sleep(1);
            return 42;
        });

        try {
            Integer result = future.get(2, TimeUnit.SECONDS);
            System.out.println("Future结果:" + result);
        } catch (Exception e) {
            e.printStackTrace();
        }

        // 关闭线程池
        executor.shutdown();
    }
}

注意:实际生产环境建议直接使用ThreadPoolExecutor自定义参数,避免使用Executors的默认实现导致资源耗尽。

方式五:Lambda表达式简化(Java 8+)

public class LambdaThread {
    public static void main(String[] args) {
        // Runnable Lambda
        new Thread(() -> System.out.println("Lambda线程")).start();

        // Callable Lambda 配合FutureTask
        FutureTask task = new FutureTask(() -> {
            return "Callable Lambda";
        });
        new Thread(task).start();
    }
}

实战案例:多线程下载模拟器

以下案例模拟多个线程同时下载文件片段,最后合并结果。

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

public class MultiThreadDownloader {
    private static final int THREAD_COUNT = 4;
    private static final int TOTAL_SIZE = 1024 * 1024 * 10; // 10MB

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = new ThreadPoolExecutor(
                THREAD_COUNT, THREAD_COUNT,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue(10),
                new ThreadPoolExecutor.CallerRunsPolicy()
        );

        CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
        AtomicInteger downloaded = new AtomicInteger(0);

        int partSize = TOTAL_SIZE / THREAD_COUNT;
        for (int i = 0; i  {
                try {
                    // 模拟下载片段
                    int size = end - start;
                    System.out.println(Thread.currentThread().getName() + 
                            " 下载片段 [" + start + "-" + end + "] 大小=" + size);
                    TimeUnit.MILLISECONDS.sleep((long)(Math.random() * 500));
                    downloaded.addAndGet(size);
                    System.out.println(Thread.currentThread().getName() + " 完成");
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                } finally {
                    latch.countDown();
                }
            });
        }

        // 等待所有线程完成
        latch.await();
        System.out.println("下载完成,总大小:" + downloaded.get());
        executor.shutdown();
    }
}

解析:使用ThreadPoolExecutor自定义线程池,CountDownLatch同步,AtomicInteger保证累加安全。

注意事项

1. 线程安全

多线程访问共享数据时必须同步,避免脏读、丢失更新。常用手段:

  • synchronized关键字(对象锁/类锁)
  • Lock接口(ReentrantLock
  • 原子类(AtomicIntegerAtomicReference
  • 并发容器(ConcurrentHashMapCopyOnWriteArrayList

2. 避免死锁

  • 保持锁顺序一致
  • 使用tryLock加超时
  • 减少锁粒度

3. 线程池使用规范

  • 拒绝使用Executors.newCachedThreadPool()(最大线程数Integer.MAX_VALUE可能导致OOM)
  • 必须手动关闭线程池(shutdown() / shutdownNow()
  • 合理配置核心线程数、最大线程数、队列大小(参考CPU密集型 vs IO密集型)

4. 正确处理中断

  • 使用Thread.currentThread().isInterrupted()检查中断状态
  • 捕获InterruptedException后应重置中断标志:Thread.currentThread().interrupt()

5. 避免启动线程的方法误用

  • 调用run()是普通方法调用,不会启动新线程
  • 启动多次start()抛出IllegalThreadStateException

6. 守护线程的陷阱

  • 守护线程内不要执行重要逻辑(如文件写入、数据库操作),因为JVM退出时会强制结束。
  • isDaemon()需在start()前设置。

总结

Java提供了灵活多样的线程创建方式,每种都有其适用场景:

  • 简单任务:实现Runnable结合Lambda。
  • 需要返回结果:实现Callable + FutureTask
  • 高并发场景:使用ThreadPoolExecutor精细控制线程资源。
  • 遗留代码:继承Thread但尽量避开。

掌握线程创建只是第一步,还需要深入理解线程安全、锁机制、并发工具类(CountDownLatchCyclicBarrierSemaphore等)以及JVM内存模型。只有将理论与实践结合,才能写出高效、稳定、可维护的并发程序。

希望本文能为你打开Java多线程编程的大门,在实际项目中灵活运用,避开常见陷阱。