在 Java 中,线程(Thread) 是程序执行的最小单元,是进程的一部分。一个进程可以包含多个线程,这些线程共享进程的内存空间(如方法区、堆),但拥有各自独立的程序计数器、虚拟机栈和本地方法栈,因此线程切换的开销远小于进程。

一、线程的核心概念

1. 线程与进程的区别
维度 进程 线程
资源占用 独立占有内存、文件句柄等 共享进程资源,仅私有少量数据(程序计数器、栈)
切换开销 大(需切换进程上下文) 小(仅切换线程上下文)
通信方式 复杂(如管道、Socket) 简单(直接操作共享变量)
独立性 高(一个进程崩溃不影响其他) 低(一个线程崩溃可能导致整个进程崩溃)
2. 线程的状态(生命周期)

Java 线程的状态定义在 Thread.State 枚举中,共 6 种,状态转换是线程的核心知识点:

状态 说明
NEW(新建) 线程对象已创建,但未调用 start() 方法(未启动)
RUNNABLE(可运行) 调用 start() 后,线程处于 “就绪” 或 “运行中” 状态(取决于 CPU 调度)
BLOCKED(阻塞) 线程因竞争同步锁(synchronized)被阻塞,等待锁释放
WAITING(等待) 线程通过 wait()join() 等方法主动放弃 CPU,需其他线程唤醒
TIMED_WAITING(超时等待) 线程通过 sleep(long)wait(long) 等方法等待指定时间,超时自动唤醒
TERMINATED(终止) 线程执行完毕(run() 方法结束)或异常终止

状态转换图核心逻辑NEW → RUNNABLE(调用 start())→ 执行 run() → TERMINATEDRUNNABLE 可因锁竞争进入 BLOCKED,获锁后回到 RUNNABLERUNNABLE 可因 sleep()/wait()/join() 进入 WAITING/TIMED_WAITING,唤醒后回到 RUNNABLE

二、Java 中创建线程的 3 种方式

1. 继承 Thread 类(重写 run() 方法)

Thread 类本身实现了 Runnable 接口,核心是重写 run() 方法(线程执行体),通过 start() 方法启动线程(而非直接调用 run())。

2. 实现 Runnable 接口(推荐)

Runnable 是函数式接口(仅含 void run() 方法),避免单继承限制,更符合 “职责分离”(线程执行逻辑与线程对象分离)。

java

运行

// 1. 实现 Runnable 接口
public class RunnableDemo 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) {
        // 2. 创建 Runnable 实例,作为 Thread 的构造参数
        Runnable task = new RunnableDemo();
        Thread t1 = new Thread(task, "线程A");
        Thread t2 = new Thread(task, "线程B");
        t1.start();
        t2.start();
    }
}

// 简化:使用 Lambda 表达式(Java 8+)
public class LambdaRunnable {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("Lambda 线程执行");
        }, "Lambda线程");
        t.start();
    }
}
3. 实现 Callable 接口(带返回值 + 可抛异常)

Runnable 无返回值、无法抛出受检异常,Callable<V> 接口弥补了这一缺陷(含 V call() throws Exception 方法),需配合 FutureTask 使用(FutureTask 实现了 RunnableFuture 接口,兼具 Runnable 和 Future 特性)。

java

运行

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

public class CallableDemo implements Callable<Integer> {
    // 重写 call() 方法,返回结果并可抛异常
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 10; i++) {
            sum += i;
            Thread.sleep(50);
        }
        return sum; // 返回计算结果
    }

    public static void main(String[] args) throws Exception {
        // 1. 创建 Callable 实例
        Callable<Integer> task = new CallableDemo();
        // 2. 包装为 FutureTask(接收返回值)
        FutureTask<Integer> futureTask = new FutureTask<>(task);
        // 3. 启动线程
        new Thread(futureTask, "计算线程").start();

        // 4. 获取返回值(get() 方法会阻塞,直到线程执行完毕)
        Integer result = futureTask.get();
        System.out.println("1-10 求和结果:" + result); // 输出 55
    }
}

核心优势:可获取线程执行结果,适合需要异步计算并返回结果的场景(如多线程下载文件后合并结果)。

三、线程的核心方法

1. 线程启动与终止
  • start():启动线程,JVM 调用 run() 方法(不能重复调用);
  • run():线程执行体,存放业务逻辑(直接调用仅为普通方法,不会启动新线程);
  • stop()(已废弃):强制终止线程,可能导致资源泄露(如锁未释放),推荐通过 “标志位” 优雅终止:

java

运行

public class StopThreadDemo implements Runnable {
    private volatile boolean isRunning = true; // volatile 保证可见性

    @Override
    public void run() {
        while (isRunning) {
            System.out.println("线程运行中...");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("线程优雅终止");
    }

    public void stop() {
        isRunning = false; // 修改标志位
    }

    public static void main(String[] args) throws InterruptedException {
        StopThreadDemo task = new StopThreadDemo();
        Thread t = new Thread(task);
        t.start();
        Thread.sleep(500); // 主线程休眠 500ms
        task.stop(); // 终止线程
    }
}
2. 线程休眠与等待
  • sleep(long millis):线程休眠指定时间(毫秒),不释放锁,休眠期间 CPU 可调度其他线程;
  • wait()/wait(long):线程主动放弃 CPU,释放锁,进入等待队列,需通过 notify()/notifyAll() 唤醒(必须在 synchronized 代码块中调用);
  • join()/join(long):让当前线程等待目标线程执行完毕后再继续(如主线程等待子线程执行完再退出):

java

运行

public class JoinDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                System.out.println("子线程:" + i);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        t.join(); // 主线程等待 t 线程执行完毕
        System.out.println("主线程继续执行"); // 子线程执行完后才输出
    }
}
3. 线程优先级与守护线程
  • 优先级:Thread.MIN_PRIORITY(1)~ Thread.MAX_PRIORITY(10),默认 Thread.NORM_PRIORITY(5),仅为 CPU 调度的 “建议”,不保证优先级高的线程先执行;
  • 守护线程(Daemon Thread):为其他线程服务(如垃圾回收线程),当所有非守护线程结束,守护线程自动退出,通过 setDaemon(true) 设置(必须在 start() 前调用):

java

运行

public class DaemonDemo {
    public static void main(String[] args) {
        Thread daemonThread = new Thread(() -> {
            while (true) {
                System.out.println("守护线程运行中...");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        daemonThread.setDaemon(true); // 设置为守护线程
        daemonThread.start();

        // 主线程(非守护线程)执行 300ms 后退出
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程退出,守护线程自动终止");
    }
}

四、线程安全问题

1. 问题根源

多个线程并发访问共享资源(如共享变量、集合),且存在修改操作,导致数据不一致(如超卖、计数错误)。

java

运行

public class ThreadDemo extends Thread {
    @Override
    public void run() {
        // 线程执行逻辑
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            try {
                Thread.sleep(100); // 线程休眠 100ms,释放 CPU(不会释放锁)
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Thread t1 = new ThreadDemo();
        Thread t2 = new ThreadDemo();
        t1.setName("线程1");
        t2.setName("线程2");
        t1.start(); // 启动线程1(底层调用 native 方法 start0(),由 JVM 调用 run())
        t2.start(); // 启动线程2
    }
}

注意

  • 不能多次调用 start(),否则抛出 IllegalThreadStateException
  • 继承 Thread 后无法再继承其他类(Java 单继承限制)。
2. 解决方式
  • 同步代码块synchronized (锁对象) { 共享资源操作 }
  • 同步方法:在方法上添加 synchronized 关键字(锁对象为 this 或类对象);
  • Lock 锁(JUC 并发包):ReentrantLock 等,比 synchronized 更灵活(可中断、超时获取锁);
  • 原子类AtomicIntegerAtomicLong 等,基于 CAS 机制实现无锁线程安全。

示例(synchronized 解决计数安全):

java

运行

public class ThreadSafeDemo {
    private static int count = 0;
    private static final Object LOCK = new Object(); // 锁对象

    public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                synchronized (LOCK) { // 同步代码块,保证原子操作
                    count++;
                }
            }
        };

        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("最终计数:" + count); // 输出 2000(线程安全)
    }
}

五、核心总结

  1. 线程是进程的最小执行单元,共享进程资源,切换开销小;
  2. 线程创建优先选择 Runnable(无单继承限制)或 Callable(带返回值);
  3. 线程状态转换是核心,需理解 RUNNABLE 与 BLOCKED/WAITING 的区别;
  4. 线程安全的核心是 “共享资源的原子操作”,通过 synchronizedLock、原子类等实现;
  5. 避免使用废弃方法(如 stop()),通过标志位、interrupt() 等实现优雅线程控制。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐