Java 多线程从 0 到 1:线程创建、生命周期与核心 API 实战

在Java开发中,多线程是提升程序性能、应对并发场景的核心技术之一。无论是服务器后台处理请求,还是桌面应用响应用户操作,多线程都扮演着至关重要的角色。本文将从基础概念出发,带大家逐步掌握线程的创建方式、理解线程的生命周期,并通过实战案例熟悉核心API的使用,真正实现Java多线程从0到1的突破。

一、为什么需要多线程?先搞懂核心价值

在单线程环境中,程序只能按顺序执行任务,一旦遇到IO操作(如网络请求、文件读取),CPU就会处于空闲状态,造成资源浪费。而多线程技术通过“并发执行”的特性,让CPU在等待IO的间隙处理其他任务,从而提升资源利用率和程序响应速度。

举个通俗的例子:单线程就像一个厨师同时负责买菜、洗菜、炒菜,中间等待水烧开的时间完全浪费;而多线程就像多个厨师分工协作,有人买菜、有人洗菜、有人炒菜,整个流程效率大幅提升。

Java中的多线程基于JVM的线程调度机制实现,底层依赖操作系统的线程管理,但Java通过封装提供了更简洁的操作接口,让开发者无需深入操作系统层面即可实现并发编程。

二、线程创建:三种核心方式及对比

Java提供了三种主流的线程创建方式,分别是继承Thread类、实现Runnable接口和实现Callable接口,它们各有特点,适用于不同场景。下面我们逐一讲解并对比分析。

1. 方式一:继承Thread类

Thread类是Java中线程的核心类,继承该类后重写run()方法,即可定义线程要执行的任务。启动线程时需调用start()方法,而非直接调用run()方法——因为start()方法会触发JVM将线程纳入调度队列,当CPU分配时间片后才会执行run()方法中的逻辑。


// 继承Thread类创建线程
class MyThread extends Thread {
    // 重写run()方法,定义线程执行逻辑
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ":执行第" + (i+1) + "次");
            try {
                // 模拟任务耗时
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 测试类
public class ThreadCreateDemo {
    public static void main(String[] args) {
        // 创建线程实例
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();
        // 设置线程名称
        thread1.setName("线程A");
        thread2.setName("线程B");
        // 启动线程
        thread1.start();
        thread2.start();
    }
}

运行结果会发现“线程A”和“线程B”的输出交替进行,这体现了线程的并发特性。但这种方式的缺点很明显:Java是单继承机制,继承Thread类后就无法再继承其他类,灵活性受限。

2. 方式二:实现Runnable接口

Runnable接口是Java提供的线程任务接口,仅包含一个run()方法。通过实现该接口定义任务逻辑,再将任务实例作为参数传入Thread类的构造方法,即可创建线程。这种方式规避了单继承的限制,是实际开发中更常用的方式。


// 实现Runnable接口定义任务
class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ":执行第" + (i+1) + "次");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 测试类
public class RunnableCreateDemo {
    public static void main(String[] args) {
        // 创建任务实例
        MyRunnable task = new MyRunnable();
        // 将任务传入Thread,创建线程
        Thread thread1 = new Thread(task, "线程C");
        Thread thread2 = new Thread(task, "线程D");
        // 启动线程
        thread1.start();
        thread2.start();
    }
}

该方式的优势是解耦了“任务逻辑”和“线程管理”,同一个任务可以被多个线程共享执行。但缺点是run()方法没有返回值,无法获取线程执行后的结果。

3. 方式三:实现Callable接口(带返回值)

为了解决Runnable接口无法返回结果的问题,Java 5引入了Callable接口。该接口的call()方法不仅可以定义任务逻辑,还能返回执行结果,并且支持抛出异常。配合FutureTask类(实现了Future接口和Runnable接口),可以获取线程执行的返回值。


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

// 实现Callable接口,指定返回值类型为Integer
class MyCallable implements Callable<Integer> {
    private int num;
    public MyCallable(int num) {
        this.num = num;
    }

    // call()方法:任务逻辑+返回值
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= num; i++) {
            sum += i;
            Thread.sleep(50);
        }
        System.out.println(Thread.currentThread().getName() + ":计算完成,结果为" + sum);
        return sum;
    }
}

// 测试类
public class CallableCreateDemo {
    public static void main(String[] args) throws Exception {
        // 1. 创建Callable任务实例
        MyCallable task1 = new MyCallable(100);
        MyCallable task2 = new MyCallable(50);

        // 2. 包装为FutureTask(用于获取返回值)
        FutureTask<Integer> futureTask1 = new FutureTask<>(task1);
        FutureTask<Integer> futureTask2 = new FutureTask<>(task2);

        // 3. 传入Thread并启动
        new Thread(futureTask1, "计算线程1").start();
        new Thread(futureTask2, "计算线程2").start();

        // 4. 获取返回值(get()方法会阻塞,直到线程执行完成)
        int result1 = futureTask1.get();
        int result2 = futureTask2.get();

        System.out.println("主线程:线程1结果=" + result1 + ",线程2结果=" + result2);
    }
}

运行结果中,主线程会等待两个计算线程完成后才输出最终结果,这是因为get()方法具有阻塞特性。该方式适用于需要获取线程执行结果的场景,如并行计算、数据分析等。

三种方式对比总结

创建方式优点缺点适用场景
继承Thread类实现简单,直接使用this获取线程单继承限制,任务与线程耦合简单场景,无需继承其他类
实现Runnable接口规避单继承,任务与线程解耦无返回值,无法抛 checked 异常大多数并发场景,推荐优先使用
实现Callable接口有返回值,支持抛异常实现稍复杂,get()方法可能阻塞需要获取线程执行结果的场景

三、线程生命周期:掌握线程的“生老病死”

线程从创建到销毁的整个过程称为线程的生命周期。Java中线程的生命周期包含6个核心状态,这些状态由JVM进行管理,开发者通过API间接控制状态转换。理解生命周期是正确使用多线程的基础,避免出现线程泄漏、死锁等问题。

1. 6个核心状态及转换关系

根据Java官方文档,线程的状态定义在Thread.State枚举中,包括以下6种:

  • 新建状态(NEW):创建线程实例后(如new Thread()),但未调用start()方法前的状态。此时线程未被JVM调度,仅存在于内存中。

  • 就绪状态(RUNNABLE):调用start()方法后,线程进入就绪状态。此时线程已被JVM纳入调度队列,等待CPU分配时间片。一旦获得时间片,就会进入运行状态。

  • 运行状态(RUNNING):线程获得CPU时间片后,执行run()方法中的逻辑。这是线程的核心工作状态。

  • 阻塞状态(BLOCKED):线程因某种原因暂时放弃CPU使用权,停止执行。常见原因包括:等待同步锁(synchronized)、调用wait()方法后未被唤醒等。阻塞状态的线程不会参与CPU调度,需等待特定条件满足后才能回到就绪状态。

  • 等待状态(WAITING):线程进入无限期等待状态,需依赖其他线程的主动唤醒(如调用notify()、notifyAll()方法)。调用Object.wait()、Thread.join()等方法会使线程进入该状态。

  • 超时等待状态(TIMED_WAITING):与等待状态类似,但存在超时时间。若超时时间到达,线程会自动回到就绪状态。调用Thread.sleep(long)、Object.wait(long)等方法会进入该状态。

  • 终止状态(TERMINATED):线程执行完run()方法或因异常退出run()方法后,进入终止状态。此时线程的生命周期结束,无法再被启动。

2. 核心状态转换图

为了更直观地理解状态转换,我们用流程图展示核心转换路径:

在这里插入图片描述

3. 关键注意点

  • 线程一旦进入终止状态(TERMINATED),再次调用start()方法会抛出IllegalThreadStateException异常。

  • sleep()方法不会释放同步锁,而wait()方法会释放同步锁。这是两者的核心区别之一。

  • 阻塞状态(BLOCKED)仅针对同步锁等待,而等待状态(WAITING/TIMED_WAITING)是更广义的等待。

四、核心API实战:从基础到进阶

Thread类及其相关接口提供了丰富的API用于线程管理,掌握这些核心API是实现多线程控制的关键。下面我们按功能分类,结合实战案例讲解常用API的使用。

1. 线程控制API(启动、休眠、中断)

(1)start()与run()

前文已提及,start()是启动线程的唯一方法,负责将线程注册到JVM调度队列;而run()仅定义线程任务逻辑,直接调用run()方法会以单线程方式执行,不会启动新线程。

(2)Thread.sleep(long millis)

使当前线程休眠指定毫秒数,进入超时等待状态(TIMED_WAITING)。休眠期间线程放弃CPU使用权,但不会释放已持有的同步锁。常用于模拟耗时任务或控制线程执行节奏。


public class SleepDemo {
    public static void main(String[] args) {
        new Thread(() -> {
            long start = System.currentTimeMillis();
            try {
                Thread.sleep(1000); // 休眠1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
                return;
            }
            long end = System.currentTimeMillis();
            System.out.println("线程休眠时间:" + (end - start) + "ms");
        }).start();
    }
}

(3)线程中断相关API

Java中没有强制中断线程的方法,而是通过“中断标志”实现协作式中断。核心API包括:

  • void interrupt():设置线程的中断标志为true,不会立即中断线程。

  • boolean isInterrupted():判断线程的中断标志是否为true(不会清除标志)。

  • static boolean interrupted():判断当前线程的中断标志是否为true(会清除标志)。

当线程处于休眠(sleep)、等待(wait)等状态时,若被中断会抛出InterruptedException异常,且中断标志会被清除。实战中,线程需定期检查中断标志,主动响应中断请求。


public class InterruptDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread taskThread = new Thread(() -> {
            // 定期检查中断标志
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("线程正在执行任务...");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    // 捕获中断异常,重新设置中断标志(因异常会清除标志)
                    System.out.println("线程被中断,准备退出");
                    Thread.currentThread().interrupt();
                }
            }
            System.out.println("线程退出");
        });

        taskThread.start();
        // 主线程休眠2秒后,中断任务线程
        Thread.sleep(2000);
        taskThread.interrupt();
    }
}

2. 线程同步与通信API

(1)synchronized关键字

synchronized是Java内置的同步锁,用于解决多线程共享资源竞争问题。它可以修饰方法或代码块,保证同一时间只有一个线程能执行被锁定的逻辑。


// 共享资源类
class ShareResource {
    private int count = 0;

    // 同步方法
    public synchronized void increment() {
        count++;
        System.out.println(Thread.currentThread().getName() + ":count=" + count);
    }
}

public class SynchronizedDemo {
    public static void main(String[] args) {
        ShareResource resource = new ShareResource();
        // 三个线程同时操作共享资源
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                for (int j = 0; j < 5; j++) {
                    resource.increment();
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "线程" + (i+1)).start();
        }
    }
}

运行结果中count会依次递增,不会出现计数混乱,这是因为synchronized保证了increment()方法的原子性。

(2)Object.wait()与Object.notify()

这两个方法是线程间通信的核心API,必须在synchronized代码块或同步方法中使用(否则会抛出IllegalMonitorStateException异常)。

  • wait():使当前线程释放同步锁,进入等待状态(WAITING),直到被其他线程调用notify()或notifyAll()唤醒。

  • notify():唤醒在此对象锁上等待的单个线程,使其进入就绪状态。

  • notifyAll():唤醒在此对象锁上等待的所有线程。

实战案例:实现“生产者-消费者”模型,生产者生产数据后通知消费者消费,消费者消费完后通知生产者继续生产。


// 共享缓冲区
class Buffer {
    private int data;
    private boolean isEmpty = true; // 缓冲区是否为空

    // 生产者生产数据
    public synchronized void produce(int data) {
        while (!isEmpty) {
            try {
                wait(); // 缓冲区非空,生产者等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.data = data;
        isEmpty = false;
        System.out.println("生产者生产:" + data);
        notify(); // 通知消费者消费
    }

    // 消费者消费数据
    public synchronized int consume() {
        while (isEmpty) {
            try {
                wait(); // 缓冲区为空,消费者等待
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        int result = this.data;
        isEmpty = true;
        System.out.println("消费者消费:" + result);
        notify(); // 通知生产者生产
        return result;
    }
}

// 生产者线程
class Producer extends Thread {
    private Buffer buffer;
    public Producer(Buffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            buffer.produce(i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 消费者线程
class Consumer extends Thread {
    private Buffer buffer;
    public Consumer(Buffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            buffer.consume();
            try {
                Thread.sleep(150);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 测试类
public class ProducerConsumerDemo {
    public static void main(String[] args) {
        Buffer buffer = new Buffer();
        new Producer(buffer).start();
        new Consumer(buffer).start();
    }
}

3. 线程属性相关API

  • void setName(String name)/String getName():设置/获取线程名称,便于调试和日志输出。

  • void setPriority(int newPriority)/int getPriority():设置/获取线程优先级(1-10,默认5)。优先级越高,获得CPU时间片的概率越大,但不保证一定优先执行(依赖操作系统调度)。

  • static Thread currentThread():获取当前正在执行的线程实例。

  • static void yield():当前线程主动放弃CPU时间片,回到就绪状态,让其他同优先级线程有机会执行。

五、总结与进阶方向

本文从线程的核心价值出发,系统讲解了三种线程创建方式的实现与对比,深入剖析了线程的6个核心状态及转换逻辑,并通过实战案例覆盖了线程控制、同步通信等核心API的使用。掌握这些内容,你已经具备了Java多线程开发的基础能力。

但多线程的学习之路并未结束,后续还可以重点关注以下进阶方向:

  1. JUC并发工具类:如ThreadPoolExecutor(线程池)、CountDownLatch(倒计时器)、CyclicBarrier(循环屏障)等,这些工具类是企业级开发的核心。

  2. 锁机制深入:如ReentrantLock(可重入锁)、ReadWriteLock(读写锁),相比synchronized更灵活,支持公平锁、中断响应等特性。

  3. 并发安全问题:如死锁、线程安全集合(ConcurrentHashMap等)、volatile关键字的内存语义。

  4. 线程池原理:线程池的核心参数、工作原理及拒绝策略,是高并发场景下性能优化的关键。

多线程开发既要掌握API的使用,更要理解其底层原理和并发思想。建议在实际开发中多写多练,结合调试工具(如JConsole、VisualVM)观察线程状态,逐步积累并发编程的经验。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

canjun_wen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值