Skip to content

线程基础

进程和线程的区别?

维度进程线程
资源隔离独立内存空间,互不干扰共享进程的堆和方法区
开销创建/切换开销大更轻量,上下文切换只需保存寄存器和栈指针
通信IPC(管道/Socket等)直接读写共享变量(需同步)
崩溃影响不影响其他进程未捕获异常可能导致整个进程终止

进程是资源分配的基本单位,线程是 CPU 调度的基本单位。

创建线程有几种方式?

1. 继承 Thread 类

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

public class Main {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();
    }
}

2. 实现 Runnable 接口(推荐)

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

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

        // Lambda 写法
        new Thread(() -> System.out.println(Thread.currentThread().getName() + " running")).start();
    }
}

3. 实现 Callable + FutureTask(有返回值)

java
class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        Thread.sleep(1000);
        return "result from " + Thread.currentThread().getName();
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
        Thread t = new Thread(futureTask);
        t.start();

        // get() 会阻塞直到线程执行完毕
        String result = futureTask.get();
        System.out.println(result);
    }
}

4. 线程池 ExecutorService

java
public class Main {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(3);

        // 提交 Runnable(无返回值)
        pool.execute(() -> System.out.println("execute: " + Thread.currentThread().getName()));

        // 提交 Callable(有返回值)
        Future<String> future = pool.submit(() -> {
            Thread.sleep(1000);
            return "result from " + Thread.currentThread().getName();
        });

        try {
            System.out.println(future.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        pool.shutdown();
    }
}

实际开发优先使用线程池,避免频繁创建和销毁。Runnable 优于继承 Thread,因为 Java 单继承限制。

start() 和 run() 的区别?

start()run()
行为让 JVM 创建新线程,新线程异步调用 run()普通方法调用,当前线程同步执行
是否新线程否,不会开启新线程
重复调用同一个 Thread 对象调用两次会抛出 IllegalThreadStateException可以多次调用,就是普通方法
java
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("执行线程: " + Thread.currentThread().getName());
    }
}

public class Main {
    public static void main(String[] args) {
        MyThread t = new MyThread();

        // 调用 start() —— 开启新线程
        t.start();
        // 输出: 执行线程: Thread-0

        // 调用 run() —— 只是在 main 线程中同步执行,没有新线程
        t.run();
        // 输出: 执行线程: main
    }
}
java
// 同一个 Thread 对象调用两次 start() 会抛异常
MyThread t = new MyThread();
t.start();
t.start(); // 抛出 IllegalThreadStateException

直接调用 run() 和多线程没有任何关系,想要真正开启线程必须调用 start()。

线程有哪些状态?

Java 线程状态定义在 Thread.State 枚举中,一共 6 种:

状态流转图

状态触发方式恢复方式
NEWnew Thread()调用 start()
RUNNABLEstart()
BLOCKED等待 synchronized 锁获得锁
WAITINGwait() / join() / LockSupport.park()notify() / unpark()
TIMED_WAITINGsleep(ms) / wait(ms)超时自动唤醒
TERMINATEDrun() 执行完毕

sleep()、wait()、yield()、join() 的区别?

所属是否释放锁行为
sleep(ms)Thread不释放让当前线程休眠指定毫秒,超时自动恢复 RUNNABLE
wait()Object释放锁必须在 synchronized 块内调用,释放锁进入 WAITING,需 notify 唤醒
yield()Thread不释放提示调度器让出 CPU,但不保证生效,线程仍然 RUNNABLE
join()Thread释放锁调用线程等待目标线程执行完毕,内部由 wait 实现,会释放持有的锁
java
// join 典型用法:main 线程等待子线程执行完毕
Thread t = new Thread(() -> {
    // 计算任务
    System.out.println("子线程计算完成");
});
t.start();
t.join(); // main 线程在此等待 t 结束后才继续
System.out.println("main 继续执行");

sleep 不释放锁,wait 会释放锁。

notify() 和 notifyAll() 区别?

notify()notifyAll()
行为随机唤醒等待队列中的一个线程唤醒所有等待队列中的线程
风险其余线程继续等待,若唤醒的线程无法继续执行可能造成死锁或饥饿所有线程重新竞争锁,只有一个能拿到
实际开发优先使用,更安全
java
// 错误:用 if 检查条件 —— 虚假唤醒或多个线程被唤醒时条件已不满足
synchronized (lock) {
    if (count == 0) {
        lock.wait(); // 被唤醒后不再检查条件,可能出错
    }
    count--;
}

// 正确:用 while 检查条件 —— 每次被唤醒都重新检查
synchronized (lock) {
    while (count == 0) {
        lock.wait(); // 被唤醒后重新检查 count,避免虚假唤醒
    }
    count--;
}

wait 必须配合 while 循环而不是 if,因为 notifyAll 唤醒后需要重新检查条件,避免虚假唤醒。