一、进程与线程基础

  1. 进程:操作系统资源分配的最小单位,一个运行的程序就是一个进程,拥有独立内存空间。进程之间相互隔离,切换开销大。
  2. 线程:进程内的执行单元,CPU调度的最小单位,一个进程可以包含多个线程;共享进程资源,线程切换开销小。
  • 多线程:同一个进程内部多条执行路径并发执行,充分利用CPU资源。
  • 单核CPU:时间片轮转,宏观并发,微观串行;多核CPU可以实现真正并行。

区别:进程是资源容器,线程是执行任务;进程隔离,线程共享。

二、多线程的优势与问题

✅ 好处

  1. 提高程序执行效率,充分利用多核CPU;
  2. 提升响应速度,GUI/服务端不会因为耗时任务卡住。

⚠️ 带来的问题

  1. 线程安全问题:多个线程同时读写共享变量,会出现数据错乱、bug。
    • 示例:多个线程同时修改同一个计数变量,最终结果不符合预期。
  2. 线程越多≠越快:线程数量过多,线程切换(上下文切换)开销变大,性能反而下降。线程数要合理。
  3. 线程异常行为: 线程执行过程抛出异常,结果分两种: ① 如果线程内部使用try‑catch捕获处理异常,线程可以继续执行,完全不会影响其他线程和整个进程。 ② 如果异常没有被捕获,只会导致当前线程终止死亡,其他线程不受影响。JVM 并不会因为单个子线程未捕获异常就直接崩溃;JVM 退出的条件是全部非守护用户线程执行完毕。

    注意:主线程 main 抛出未捕获异常也只是主线程结束,只要还有其他存活的用户线程,进程依旧继续运行。只有发生底层 JVM 致命错误,或者代码手动调用System.exit(),才会直接让整个进程崩溃退出InfoQ...。

    开发坑:主线程的 try‑catch 无法捕获子线程内部抛出的异常;子线程异常不会向上传播到主线程,需要在线程内部处理,或者配置UncaughtExceptionHandler处理器。

三、Java创建线程的两种基础方式

方式1:继承Thread类

  1. 类继承Thread
  2. 重写run()方法:线程执行的业务逻辑;
  3. 调用start()启动线程,不是直接调用run()

    run()只是普通方法;start()才会向操作系统申请创建新线程。

class MyThread extends Thread{
    @Override
    public void run() {
        //线程执行代码
    }
}
//启动
new MyThread().start();

方式2:实现Runnable接口

  1. 实现Runnable接口,重写run;
  2. 把Runnable实例传入Thread构造器;
  3. 调用start()启动。

    推荐Runnable:避免单继承限制,更适合资源共享。

class MyTask implements Runnable{
    @Override
    public void run() {
        //任务逻辑
    }
}
new Thread(new MyTask()).start();

四、线程常用API

  1. sleep(long ms):静态方法,让当前线程休眠指定毫秒。

    ✅重点:sleep 仅仅暂停线程运行,不会释放当前线程持有的synchronized。线程抱着锁休眠,其他线程无法获取这把锁,只能阻塞等待,直到同步代码块执行结束锁才释放。

    ⚠️容易混淆:wait()方法会释放锁;sleep 不会释放锁。

  2. 线程异常:线程运行时异常不会向外抛给main线程,需要线程内部自己捕获处理。
  3. main线程:程序入口主线程;main结束不代表程序结束,只要还有非守护线程,JVM继续运行。

守护线程 daemon

  • setDaemon(true) 设置为守护线程。
  • 用户线程:普通业务线程;守护线程:为用户线程服务(GC垃圾回收)。
  • 规则:所有用户线程全部结束,JVM直接退出,守护线程直接被终止,不会完整执行完毕

五、线程状态(生命周期)

  1. 新建NEW:new Thread(),还没start
  2. 就绪RUNNABLE:调用start(),等待CPU分配时间片
  3. 运行RUNNING:抢到CPU时间片,执行run方法
  4. 阻塞BLOCKED/WAITING/TIMED_WAITING:sleep、等待锁、wait等,让出CPU
  5. 终止TERMINATED:run执行完毕 / 异常退出,线程结束,不能再次start。

线程一旦结束,不能重启,只能新建线程对象。

六、并发场景:为什么会线程不安全

核心条件同时满足就会线程不安全:

  1. 多线程环境
  2. 共享资源(共享变量)
  3. 对资源进行读写修改操作

案例:多个线程同时修改同一个计数器,最终数值小于预期。 解决思路:加锁synchronized,保证同一时刻只有一个线程执行临界区代码。

七、服务端视角:多线程在服务端的作用

  1. 服务端程序(如MySQL、Web服务)大量使用多线程;
  2. 每来一个客户端请求,分配一个线程处理;
  3. 优势:可以同时处理大量客户端连接;
  4. 痛点:连接量巨大时,频繁创建销毁线程开销大,引出线程池

八、总结

  1. 多线程不是银弹,不是线程开越多速度越快;上下文切换有成本。
  2. 线程安全是多线程开发最大坑点,只要共享可变数据就要警惕。
  3. start()run()一定要分清:run只是普通方法,start才真正创建操作系统线程。
  4. 守护线程适合做后台辅助任务,不能用来执行必须完成的业务。
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐