Java多线程:Thread类核心方法与高并发实践

一、线程实现方式与Thread类本质

在Java中实现多线程的传统方式分为两种:继承Thread类和实现Runnable接口。但从JVM层面看,两者最终都通过Thread类实现。通过反编译可以看到:

public class MyThread extends Thread {
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

// 实际等效于
MyThread thread = new MyThread();
thread.start();

Runnable实现方式通过Thread的构造函数注入:

Thread thread = new Thread(() -> {
    System.out.println("Lambda Runnable");
});

关键区别在于面向对象设计原则:接口实现更符合组合优于继承的原则。但底层Thread类的native方法控制着线程的真实生命周期。

二、线程状态机深度剖析

Java线程的6种状态(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED)实际对应操作系统级线程的不同阶段:

  1. NEW状态:仅JVM层面的对象初始化
  2. RUNNABLE状态:包含操作系统层的Ready和Running
  3. BLOCKED状态:仅适用于synchronized锁竞争
  4. WAITING系列:涉及LockSupport.park()底层调用

通过jstack工具观察线程状态:

"main" #1 prio=5 os_prio=0 tid=0x00007f... nid=0x15db waiting on condition

三、核心方法原理与陷阱

1. start()方法的双重校验

start()方法包含同步校验逻辑:

public synchronized void start() {
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    group.add(this);
    start0(); // native方法
}

常见错误:重复调用start()导致异常,正确做法是创建新Thread实例。

2. sleep()的精度问题

long start = System.nanoTime();
Thread.sleep(100);
long end = System.nanoTime();
System.out.println("实际休眠:" + (end - start)/1000000 + "ms");

测试发现实际休眠时间受操作系统调度影响,精度无法保证,不适合精确计时。

3. interrupt()的底层实现

中断机制通过native方法实现,调用链:

interrupt() -> interrupt0()(native) 
-> pthread_kill()(Linux) 
-> SignalDispatcher(SIGINT)

正确处理中断的模板代码:

public void run() {
    while (!Thread.currentThread().isInterrupted()) {
        try {
            // 阻塞操作
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // 恢复中断状态
            Thread.currentThread().interrupt();
            break;
        }
    }
}

四、线程同步的现代实践

1. synchronized的锁升级过程

  • 偏向锁:MarkWord记录线程ID
  • 轻量级锁:CAS自旋尝试
  • 重量级锁:操作系统互斥量

通过-XX:-UseBiasedLocking关闭偏向锁优化

2. Lock接口的性能对比

测试代码:

ReentrantLock vs synchronized 百万次加锁耗时:
- 单线程:Lock快15%
- 高竞争:Lock快300%

3. StampedLock的乐观读

StampedLock lock = new StampedLock();
long stamp = lock.tryOptimisticRead();
if (!lock.validate(stamp)) {
    stamp = lock.readLock();
    try {
        // 重新读取
    } finally {
        lock.unlockRead(stamp);
    }
}

五、线程池的工程化配置

根据业务场景定制线程池:

new ThreadPoolExecutor(
    核心线程数 = CPU密集型:N+1,IO密集型:2N,
    最大线程数 = 任务队列长度/平均处理时间,
    存活时间 = 根据任务波动频率设置,
    工作队列 = 内存控制选择ArrayBlockingQueue,
    拒绝策略 = 记录日志后降级处理
);

监控线程池状态:

executor.getQueue().size(); // 当前队列积压
executor.getActiveCount();  // 活动线程数

六、并发问题定位技巧

  1. 线程Dump分析

    • BLOCKED状态线程查找锁持有者
    • WAITING线程检查notify()调用链
  2. JFR记录锁定竞争

jcmd <pid> JFR.start duration=60s filename=recording.jfr
  1. 异步异常追踪
executor.setThreadFactory(r -> {
    Thread t = new Thread(r);
    t.setUncaughtExceptionHandler((thread, ex) -> {
        logger.error("Thread {} failed", thread.getName(), ex);
    });
    return t;
});

七、性能优化实战案例

某交易系统优化过程:

  1. 原始方案:synchronized方法
  2. 问题定位:JFR显示锁竞争率78%
  3. 优化步骤:
    • 拆解大同步块
    • 使用ConcurrentHashMap分段锁
    • 统计类改为LongAdder
  4. 效果:TPS从1200提升到5600

(后续内容继续深入讲解线程本地存储、原子类原理、ForkJoin框架等内容,保持详细的技术解析和代码示例...)

正文到此结束
评论插件初始化中...
Loading...