【JUC】关于 Java 线程介绍

Java 中的线程是并发编程的核心。下面从基础概念到实际使用,系统地讲一讲。

一、进程 vs 线程

  • 进程:操作系统分配资源的基本单位,每个进程有独立的内存空间。
  • 线程 :CPU 调度的基本单位,同一进程内的多个线程共享进程的内存空间 (堆、方法区),但各自有独立的栈 和程序计数器。

线程更轻量,创建和切换开销比进程小,但共享内存也带来了线程安全问题。

二、创建线程的方式

1. 继承 Thread 类

java 复制代码
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程运行中:" + Thread.currentThread().getName());
    }
}

new MyThread().start();  // 注意是 start() 不是 run()
  • start() 会启动新线程并让 JVM 调用 run();
  • 直接调用 run() 只是普通方法调用,不会开新线程。

2. 实现 Runnable 接口(推荐)

java 复制代码
Runnable task = () -> System.out.println("任务执行");
new Thread(task).start();

优点:避免单继承限制,任务与线程解耦。

3. 实现 Callable + FutureTask

java 复制代码
Callable<Integer> callable = () -> 1 + 1;
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
Integer result = futureTask.get();  // 阻塞获取返回值

相比 Runnable,可以有返回值、可以抛异常。

4. 线程池(实际开发最常用)

java 复制代码
ExecutorService pool = Executors.newFixedThreadPool(4);
pool.submit(() -> System.out.println("池中线程执行"));
pool.shutdown();

生产环境推荐用 ThreadPoolExecutor 手动创建,避免 Executors 的坑(如无界队列导致 OOM)。

三、线程的生命周期

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

状态 说明
NEW 创建了但未调用 start()
RUNNABLE 可运行(包括正在运行和就绪等待 CPU)
BLOCKED 等待获取 synchronized 锁
WAITING 无限期等待(wait()、join()、park())
TIMED_WAITING 限时等待(sleep、wait(ms)、join(ms))
TERMINATED 已结束

转换示意:

复制代码
NEW → start() → RUNNABLE ⇄ BLOCKED/WAITING/TIMED_WAITING → TERMINATED

四、常用方法

方法 作用
start() 启动线程
run() 线程执行体
sleep(ms) 让当前线程休眠,不释放锁
join() 等待该线程结束
yield() 让出 CPU,回到就绪状态
interrupt() 中断线程(设置中断标志)
isInterrupted() 检查中断标志
setDaemon(true) 设为守护线程(JVM 退出时不管它)

⚠️ 已废弃 :stop()、suspend()、resume(),容易造成死锁或数据不一致。

五、线程安全问题

多个线程访问共享可变数据时可能出错。经典例子:

java 复制代码
class Counter {
    private int count = 0;
    public void increment() { count++; }  // 非原子操作
}

count++ 实际是「读-改-写」三步,多线程下会丢失更新。

解决方案

1. synchronized(内置锁)

java 复制代码
public synchronized void increment() { count++; }
// 或
synchronized (lock) { count++; }

2. Lock(显式锁)

java 复制代码
Lock lock = new ReentrantLock();
lock.lock();
try { count++; } finally { lock.unlock(); }

比 synchronized 更灵活(可中断、可超时、公平锁)。

3. 原子类(CAS)

java 复制代码
AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet();

基于 CAS,无锁,性能好。

4. volatile

保证可见性 和有序性 ,但不保证原子性。适合状态标志位。

六、线程间通信

wait / notify / notifyAll

必须在 synchronized 块中调用,且锁对象一致。

java 复制代码
synchronized (lock) {
    while (!condition) lock.wait();  // 释放锁并等待
    // 处理
    lock.notifyAll();                // 唤醒其他线程
}

⚠️ 用 while 而非 if,防止虚假唤醒。

其他工具

  • CountDownLatch:等待 N 个任务完成
  • CyclicBarrier:一组线程互相等待
  • Semaphore:控制并发数量
  • BlockingQueue:生产者-消费者模型

七、JMM(Java 内存模型)

理解线程必须了解 JMM:

  • 每个线程有自己的工作内存 ,共享变量存在主内存;
  • 线程读写变量要经过工作内存,导致可见性问题;
  • happens-before 规则保证某些操作的可见性(如锁的释放先于获取、volatile 写先于读)。

八、最佳实践

  1. 优先用线程池,不要频繁 new Thread。
  2. 给线程起名字,方便排查问题。
  3. 缩小锁范围,避免锁嵌套。
  4. 优先用并发容器(ConcurrentHashMap、CopyOnWriteArrayList)代替同步包装。
  5. 避免死锁:按固定顺序获取锁、使用超时锁。
  6. 捕获异常 :线程内未捕获异常会导致线程静默死亡,可设置 UncaughtExceptionHandler。
相关推荐
2301_780789666 小时前
跨平台适配!SDK 游戏盾兼容全终端游戏场景
java·网络·ddos
第九序言T7 小时前
Python 数据处理进阶(十八)批量处理同结构文件:一个文件夹一次跑完
开发语言·python·自动化·pandas·数据处理
学长毕设7 小时前
基于springboot的食品仓库管理系统
java·spring boot·后端
步行cgn8 小时前
FormHttpMessageConverter 详解:Spring MVC 中表单数据的读写转换器
java·spring·mvc
龙亘川8 小时前
构建城市公共安全数字底座:亘川智城智慧安防系统全维度解读
java·大数据·开发语言·人工智能·信息可视化
金銀銅鐵8 小时前
[Java] 用 CLI 展示 EnumMap 增删键值对时的内部变化
java·源码·ai编程
程序喵大人8 小时前
【C++入门】内存模型与资源管理 - 09 shared_ptr 和 weak_ptr 解决共享,也带来代价
开发语言·c++·资源管理·智能指针·内存模型
秋天的怀念_David8 小时前
第二十二夜 · 数字迷雾
java·程序人生·算法
Raas1008 小时前
AI网关部署方案?从部署到上线全流程
java·开发语言·人工智能·gateway·ai网关·企业级ai网关·企业级ai产品
niucloud-admin9 小时前
NIUCLOUD PHP SAAS上门家政使用文档:城市管理
java·前端·php