Java 多线程全解析:从基础到进阶

一、认识线程

1.1 线程的概念

线程是操作系统中的执行流,多个线程可以 "同时" 执行多份代码。就像公司办理业务时,多个会计分别处理不同任务,共同完成公司的整体业务。其中,启动其他线程的线程称为主线程(Main Thread)。

1.2 为什么需要线程

  • 充分利用多核 CPU:单核 CPU 发展遇到瓶颈,多核 CPU 需要并发编程来提高算力
  • 处理 IO 等待:在等待 IO 操作时,可以利用线程处理其他工作
  • 轻量级:相比进程,线程的创建、销毁和调度成本更低

1.3 进程与线程的区别

特性进程线程
资源分配系统分配资源的最小单位系统调度的最小单位
内存空间进程间不共享内存同一进程的线程共享内存
独立性进程崩溃不影响其他进程线程崩溃可能导致整个进程崩溃
包含关系包含线程,至少有一个主线程属于进程

1.4 Java 线程与操作系统线程的关系

Java 中的Thread类是对操作系统线程 API 的封装和抽象,操作系统内核实现了线程机制并提供 API(如 Linux 的 pthread 库),Java 通过Thread类简化了线程操作。

二、创建线程的方法

2.1 继承 Thread 类

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("线程运行的代码");
    }
}

// 使用
MyThread t = new MyThread();
t.start();  // 启动线程

2.2 实现 Runnable 接口

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程运行的代码");
    }
}

// 使用
Thread t = new Thread(new MyRunnable());
t.start();  // 启动线程

2.3 其他创建方式

  • 匿名内部类创建 Thread 子类对象
  • 匿名内部类创建 Runnable 子类对象
  • Lambda 表达式创建 Runnable 对象
// Lambda表达式示例
Thread t = new Thread(() -> System.out.println("使用Lambda创建线程"));
t.start();

三、Thread 类的常见方法

3.1 构造方法

方法说明
Thread()创建线程对象
Thread(Runnable target)使用 Runnable 对象创建线程
Thread(String name)创建指定名称的线程
Thread(Runnable target, String name)使用 Runnable 对象创建指定名称的线程

3.2 常用属性及获取方法

属性获取方法说明
IDgetId()线程唯一标识
名称getName()线程名称,用于调试
状态getState()线程当前状态
优先级getPriority()优先级高的线程更容易被调度
后台线程isDaemon()JVM 在所有非后台线程结束后退出
是否存活isAlive()线程的 run () 方法是否运行结束
是否中断isInterrupted()线程中断状态

3.3 关键方法

  • start():启动线程,使线程开始执行
  • run():线程执行的任务代码
  • interrupt():中断线程
  • join():等待线程执行完毕
  • currentThread():获取当前线程对象
  • sleep(long millis):使当前线程休眠指定毫秒数

四、线程状态及转换

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

  1. NEW:线程已创建但未启动
  2. RUNNABLE:可运行状态,包括正在运行和就绪状态
  3. BLOCKED:阻塞状态,等待获取锁
  4. WAITING:无限期等待状态
  5. TIMED_WAITING:限时等待状态
  6. TERMINATED:线程执行完毕

状态转换关系:

  • 调用start()方法:NEW → RUNNABLE
  • 线程执行完毕:RUNNABLE → TERMINATED
  • 调用sleep()wait(timeout):RUNNABLE → TIMED_WAITING
  • 调用wait():RUNNABLE → WAITING
  • 等待锁时:RUNNABLE → BLOCKED

五、线程安全问题

5.1 线程不安全的原因

  1. 线程调度随机性:多线程执行顺序不确定
  2. 共享数据修改:多个线程修改同一变量
  3. 原子性问题:操作不是原子的,可能被中断
  4. 可见性问题:一个线程的修改不能被其他线程及时看到
  5. 指令重排序:编译器优化导致指令执行顺序变化

5.2 解决线程安全问题

5.2.1 synchronized 关键字

synchronized保证原子性,提供互斥访问:

// 修饰代码块
synchronized (lockObject) {
    // 需要同步的代码
}

// 修饰方法
public synchronized void method() {
    // 需要同步的代码
}

synchronized特性:

  • 互斥性:同一时间只有一个线程能执行同步代码
  • 可重入性:同一线程可多次获取同一把锁
  • 自动释放:代码块执行完毕自动释放锁
5.2.2 volatile 关键字

volatile保证内存可见性,使变量修改能被其他线程及时看到:

private volatile int count = 0;

注意:volatile不保证原子性,仅解决可见性问题。

六、锁策略

6.1 乐观锁 vs 悲观锁

  • 悲观锁:假设冲突会发生,每次操作都加锁
  • 乐观锁:假设冲突很少发生,操作时不加锁,提交时检查冲突

synchronized初始使用乐观锁,冲突频繁时转为悲观锁。

6.2 重量级锁 vs 轻量级锁

  • 重量级锁:依赖操作系统 mutex,涉及用户态与内核态切换
  • 轻量级锁:在用户态完成加锁,冲突严重时升级为重量级锁

6.3 自旋锁

获取锁失败时不阻塞,而是循环尝试获取锁,适用于锁持有时间短的场景。

6.4 公平锁 vs 非公平锁

  • 公平锁:按请求顺序获取锁
  • 非公平锁:不按顺序,允许 "插队"

synchronized是非公平锁,ReentrantLock可通过构造参数指定公平性。

6.5 读写锁

分离读操作和写操作,允许多个读操作同时进行,读与写、写与写互斥。Java 中通过ReentrantReadWriteLock实现。

七、CAS 操作

7.1 什么是 CAS

CAS(Compare and Swap,比较并交换)是一种原子操作,包含三个操作数:内存地址、预期值和新值。只有当内存中的值等于预期值时,才将新值写入该地址。

7.2 CAS 的应用

  1. 实现原子类:如AtomicInteger
  2. 实现自旋锁

7.3 ABA 问题

CAS 无法区分值是一直未变还是变化后又变回原值。解决方法是引入版本号,Java 中可使用AtomicStampedReference

八、JUC 常用类

8.1 Callable 与 FutureTask

Callable用于描述有返回值的任务,FutureTask用于获取任务结果:

Callable<Integer> callable = () -> {
    int sum = 0;
    for (int i = 1; i <= 100; i++) {
        sum += i;
    }
    return sum;
};

FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
int result = futureTask.get();  // 获取结果,会阻塞

8.2 ReentrantLock

可重入锁,与synchronized相比更灵活:

ReentrantLock lock = new ReentrantLock();
lock.lock();
try {
    // 临界区
} finally {
    lock.unlock();  // 必须手动释放锁
}

8.3 线程池

线程池管理线程生命周期,减少创建销毁开销:

// 创建固定大小的线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
// 提交任务
pool.submit(() -> System.out.println("执行任务"));
// 关闭线程池
pool.shutdown();

ThreadPoolExecutor构造参数详解:

  • corePoolSize:核心线程数
  • maximumPoolSize:最大线程数
  • keepAliveTime:非核心线程空闲时间
  • workQueue:任务队列
  • threadFactory:线程工厂
  • handler:拒绝策略

8.4 其他工具类

  • Semaphore:信号量,控制并发访问数量
  • CountDownLatch:等待多个任务完成

九、死锁及避免

9.1 死锁产生的条件

  1. 互斥使用
  2. 不可抢占
  3. 请求和保持
  4. 循环等待

9.2 避免死锁的方法

最有效的方法是破坏循环等待条件,例如对锁进行编号,按固定顺序获取锁。

十、线程安全的集合类

10.1 常用线程安全集合

  • ConcurrentHashMap:线程安全的 HashMap
  • CopyOnWriteArrayList:读多写少场景的线程安全 List
  • 阻塞队列:ArrayBlockingQueueLinkedBlockingQueue

10.2 ConcurrentHashMap 特点

  • 读操作不加锁,使用 volatile 保证可见性
  • 写操作针对每个哈希桶加锁,降低锁冲突
  • 优化扩容方式,提高并发性能

总结

多线程编程是 Java 开发中的重要知识点,掌握线程的创建、同步机制、锁策略及 JUC 工具类的使用,能够编写高效且线程安全的并发程序。在实际开发中,需根据具体场景选择合适的并发策略,避免线程安全问题和性能瓶颈。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值