并发编程基础概念
侧边栏壁纸
  • 累计撰写 36 篇文章
  • 累计收到 1 条评论

并发编程基础概念

ASN__
2026-04-09 / 0 评论 / 8 阅读 / 正在检测是否收录...

一、并发编程基础概念

1.1 并发与并行

  • 并发:多任务交替执行(单核/多核),依赖 CPU 时间片轮转。宏观上是同时进行的,微观上是串行。
  • 并行:多任务同时执行(必须多核),利用多个 CPU 核心同时处理不同任务。

1.2 Java 内存模型 (JMM)

  • 主内存:所有线程共享,存储实例变量、静态变量。
  • 工作内存:每个线程私有,存储主内存变量的副本。线程不能直接操作主内存,必须先读入工作内存,操作完成后再写回。

JMM 定义的 8 种内存交互操作(原子指令):

  1. lock(锁定):作用于主内存,将变量标识为线程独占。
  2. unlock(解锁):作用于主内存,释放独占状态。
  3. read(读取):作用于主内存,将变量值传输到工作内存。
  4. load(载入):作用于工作内存,将 read 的值放入变量副本。
  5. use(使用):作用于工作内存,将变量值传递给执行引擎。
  6. assign(赋值):作用于工作内存,将执行引擎的值赋给变量副本。
  7. store(存储):作用于工作内存,将变量值传送到主内存。
  8. write(写入):作用于主内存,将 store 的值写入主内存变量。

1.3 线程安全问题的根源与表现

  • 根源:主内存与工作内存数据不一致。
  • 竞态条件:结果依赖线程执行的相对时序(如 check-then-act)。
  • 数据竞争:多线程访问同一内存,至少一个写操作,且无同步。
  • 死锁:两个及以上线程互相持有对方需要的锁,永久阻塞。

二、并发编程三大特性(详细版)

特性详细描述实现手段
可见性线程 A 修改变量后,线程 B 能立即看到变化。volatilesynchronizedLock(锁释放前刷新主存)、final
原子性一个或多个操作要么全执行,要么全不执行,中间不被调度中断。synchronizedLockjava.util.concurrent.atomic 原子类
有序性避免指令重排序带来的逻辑错误。单线程内语义表现为串行。volatile(内存屏障)、synchronized(锁释放与获取原则)

三、线程基础 API 详解

3.1 创建线程的方式(含代码细节)

方式一:继承 Thread 类

public class ThreadExample extends Thread {
    @Override
    public void run() {
        System.out.println("Thread is running");
    }
    public static void main(String[] args) {
        ThreadExample t = new ThreadExample();
        t.start();
    }
}

方式二:实现 Runnable 接口

public class RunnableExample implements Runnable {
    @Override
    public void run() {
        System.out.println("Runnable task is running");
    }
    public static void main(String[] args) {
        Thread t = new Thread(new RunnableExample());
        t.start();
        // Lambda 简化写法
        new Thread(() -> System.out.println("Lambda style")).start();
    }
}

方式三:实现 Callable 接口(有返回值、可抛异常)

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class CallableExample implements Callable<String> {
    @Override
    public String call() throws Exception {
        Thread.sleep(1000);
        return "Callable result";
    }
    public static void main(String[] args) throws Exception {
        FutureTask<String> futureTask = new FutureTask<>(new CallableExample());
        new Thread(futureTask).start();
        // 阻塞获取结果
        System.out.println(futureTask.get()); 
    }
}

3.2 Thread 类构造方法与核心控制

构造方法细节

  • Thread():默认线程名 "Thread-" + n
  • Thread(String name):指定线程名。
  • Thread(Runnable target):指定任务。
  • Thread(Runnable target, String name):指定任务与名称。

线程优先级与守护线程

Thread t = new Thread();
t.setPriority(Thread.MAX_PRIORITY); // 10(最高)
t.setPriority(Thread.NORM_PRIORITY); // 5(默认)
t.setPriority(Thread.MIN_PRIORITY); // 1(最低)

// 守护线程:JVM 退出时不管守护线程是否结束
Thread daemonT = new Thread(() -> {
    while (true) { /* 后台任务,如日志清理 */ }
});
daemonT.setDaemon(true); // 必须在 start() 前设置
daemonT.start();

3.3 线程中断机制(方法签名详解)

  • void interrupt():设置中断标志位为 true
  • boolean isInterrupted():检查标志位,不清除
  • static boolean interrupted():检查当前线程标志位,并清除(重置为 false)。
Thread t = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        // 业务逻辑
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // 捕获异常后标志位会被清除,需手动再次中断以退出循环
            Thread.currentThread().interrupt();
        }
    }
});

四、synchronized 锁机制深度解析

4.1 锁的三种形态(代码示例)

对象锁(锁当前实例 this):

class SyncDemo {
    // 同步实例方法
    public synchronized void method1() { /* 锁 this */ }
    
    // 同步代码块
    public void method2() {
        synchronized (this) { /* 锁 this */ }
    }
}

类锁(锁 Class 对象):

class SyncDemo {
    // 同步静态方法
    public static synchronized void staticMethod1() { /* 锁 SyncDemo.class */ }
    
    public void staticMethod2() {
        synchronized (SyncDemo.class) { /* 锁类对象 */ }
    }
}

4.2 锁升级机制(Java 6+,不可逆)

  1. 偏向锁:无竞争时,在对象头记录线程 ID,线程再次进入仅需检查 ID,无需 CAS。
  2. 轻量级锁:交替执行时,通过 CAS 自旋尝试获取锁,不阻塞线程(消耗 CPU)。
  3. 重量级锁:激烈竞争时,自旋失败升级为 OS 互斥量,线程挂起进入阻塞队列(开销大)。

五、volatile 与内存屏障

5.1 volatile 特性总结

  • 保证可见性:写 volatile 变量时强制刷新工作内存到主内存;读 volatile 变量时强制从主内存加载。
  • 禁止重排序:通过插入内存屏障防止 CPU/编译器对指令进行重排。
  • 不保证原子性i++ 操作(读-改-写)依然不安全。

5.2 内存屏障分类(JMM 规范)

屏障类型作用示例场景
LoadLoad屏障前读操作完成,才能执行屏障后读操作。读变量A; LoadLoad; 读变量B;
LoadStore屏障前读操作完成,才能执行屏障后写操作。读变量A; LoadStore; 写变量B;
StoreStore屏障前写操作完成并可见,才能执行屏障后写操作。写变量A; StoreStore; 写变量B;
StoreLoad屏障前写操作完成并可见,才能执行屏障后读操作。开销最大,兼具其他三者功能。写变量A; StoreLoad; 读变量B;

volatile 屏障插入策略

  • volatile 写:前插 StoreStore,后插 StoreLoad。
  • volatile 读:后插 LoadLoad + LoadStore。

六、Lock 接口与 ReentrantLock 详细 API

6.1 Lock 接口方法清单

方法详细描述
void lock()阻塞获取锁,不可中断,死等。
void lockInterruptibly()可响应中断的获取锁,等待时若线程被中断则抛 InterruptedException
boolean tryLock()非阻塞,获取到返回 true,否则立即返回 false
boolean tryLock(long time, TimeUnit unit)超时等待,超时返回 false;等待时可响应中断。
void unlock()释放锁。必须在 finally 块中调用
Condition newCondition()返回绑定此锁的条件变量。

6.2 ReentrantLock 使用模板

class X {
    private final ReentrantLock lock = new ReentrantLock();
    
    public void m() {
        lock.lock();  // block until condition holds
        try {
            // ... method body
        } finally {
            lock.unlock(); // 关键:防止死锁
        }
    }
}

6.3 公平锁与非公平锁构造

// 非公平锁(默认,吞吐量高)
ReentrantLock unfairLock = new ReentrantLock();
ReentrantLock unfairLock2 = new ReentrantLock(false);

// 公平锁(FIFO,避免线程饥饿但上下文切换多)
ReentrantLock fairLock = new ReentrantLock(true);

6.4 可中断与超时示例

// 可中断锁示例
try {
    lock.lockInterruptibly();
    // 临界区
} catch (InterruptedException e) {
    // 线程被中断,退出等待队列
} finally {
    lock.unlock();
}

// 超时锁示例
if (lock.tryLock(2, TimeUnit.SECONDS)) {
    try {
        // 获取成功
    } finally {
        lock.unlock();
    }
} else {
    // 执行备选方案
}

七、读写锁与 StampedLock

7.1 ReentrantReadWriteLock 特性

  • 读写互斥:读锁与写锁互斥。
  • 锁降级:持有写锁的线程可以获取读锁,然后释放写锁,降级为读锁。
  • 不支持锁升级:持有读锁时直接获取写锁会导致死锁,JVM 禁止此行为。
ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();
Lock writeLock = rwLock.writeLock();

// 缓存更新场景:锁降级示例
writeLock.lock();
try {
    // 1. 更新数据库/缓存
    readLock.lock(); // 在释放写锁前获取读锁
} finally {
    writeLock.unlock(); // 降级为读锁
}
try {
    // 2. 后续读取操作(保证数据可见性)
} finally {
    readLock.unlock();
}

7.2 StampedLock 三种模式(Java 8+,不可重入)

  1. 写锁long stamp = stampedLock.writeLock(); 返回邮戳。
  2. 悲观读锁long stamp = stampedLock.readLock();
  3. 乐观读:无锁机制,依赖版本校验。
class Point {
    private double x, y;
    private final StampedLock sl = new StampedLock();

    // 乐观读(读多写少性能最优)
    double distanceFromOrigin() {
        long stamp = sl.tryOptimisticRead(); // 获取乐观读邮戳
        double currentX = x, currentY = y;    // 无锁读取
        if (!sl.validate(stamp)) {            // 校验邮戳是否失效
            stamp = sl.readLock();            // 失效则升级为悲观读
            try {
                currentX = x;
                currentY = y;
            } finally {
                sl.unlockRead(stamp);
            }
        }
        return Math.sqrt(currentX * currentX + currentY * currentY);
    }
}

八、Condition 条件变量(替换 Object Monitor)

8.1 核心方法对比

Condition 方法Object 对应方法说明
await()wait()释放锁并等待。必须在 while 循环中调用以防虚假唤醒。
signal()notify()唤醒一个等待线程。
signalAll()notifyAll()唤醒所有等待线程。
awaitUninterruptibly()不可中断等待。
awaitUntil(Date deadline)wait(long timeout)带截止时间等待。

8.2 生产者-消费者模型(多条件队列)

class BoundedBuffer {
    final Lock lock = new ReentrantLock();
    final Condition notFull  = lock.newCondition(); 
    final Condition notEmpty = lock.newCondition(); 

    // 防御性编程:必须用 while 循环
    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length) // 防止虚假唤醒
                notFull.await();
            // ... 入队逻辑
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }
}

九、原子类与 CAS 底层原理

9.1 AtomicInteger 常用方法

方法签名操作描述
int get()获取当前值。
void set(int newValue)设置新值(无原子性保证,仅可见性)。
int getAndSet(int newValue)设置新值,返回旧值。
boolean compareAndSet(expect, update)CAS 核心操作。
int getAndIncrement()等价于 i++
int incrementAndGet()等价于 ++i
int getAndAdd(int delta)加 delta 并返回旧值。

9.2 ABA 问题与解决方案

ABA 问题描述:线程 1 读取值 A,线程 2 改为 B,线程 3 又改回 A。线程 1 使用 CAS 检查时发现值仍为 A,误以为未变化而更新成功。

解决方案AtomicStampedReference 引入版本号

// 泛型类:AtomicStampedReference<V>
AtomicStampedReference<Integer> ref = 
    new AtomicStampedReference<>(100, 0); // 初始值100,版本号0

int stamp = ref.getStamp();
int oldValue = ref.getReference();

// 更新时需同时匹配值和时间戳
boolean success = ref.compareAndSet(oldValue, 200, stamp, stamp + 1);
0

评论

博主关闭了所有页面的评论