【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。
相关推荐
HelloWorld工程师1 小时前
@Primary 注解
java·spring
geovindu1 小时前
rust: Borg Pattern(续)
开发语言·后端·设计模式·rust·博格模式
aqiu1111113 小时前
【C++算法打怪专栏】8. 字符串转换整数 (atoi)
开发语言·c++·算法
爱吃香菜的初学者3 小时前
十四.Linux——消息队列
linux·运维·开发语言
迅猛龙办公室3 小时前
python实现倒序输出数字
开发语言·python
夏幻灵9 小时前
JavaScript this 面试:五种绑定规则、优先级与常见面试陷阱
开发语言·javascript·面试
程序猿编码9 小时前
榨干 RTX5090 算力!Qwen3 专用单卡推理引擎,手写C++/CUDA 算子实现 MTP 推测解码
开发语言·c++·大模型推理·qwen3·多模态推理
bkspiderx11 小时前
Qt 插件机制:动态扩展应用功能的核心框架
开发语言·qt·元数据·qt 插件·qpluginloader
代码什么用11 小时前
Spring基础使用
java·后端·spring