Java多线程编程与同步器详解

线程基础与生命周期

线程基本概念

线程是程序执行的最小单位,Java中通过Thread类或实现Runnable接口来创建线程。每个线程独立执行代码逻辑,其生命周期从start()方法调用开始,实际执行逻辑定义在run()方法中。Java 8起还支持无参数且返回void的方法引用作为线程目标。

// 通过继承Thread类创建线程
class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Thread running");
    }
}

// 通过Runnable接口创建线程
Runnable task = () -> System.out.println("Runnable running");
Thread thread = new Thread(task);

线程类型与优先级

Java线程分为两类:

  1. 用户线程(非守护线程):JVM会等待所有用户线程结束后才退出
  2. 守护线程:不会阻止JVM退出,通常用于后台支持任务

线程优先级是1-10的整数(默认为5),数值越高表示线程越重要。但需注意:

  • 优先级仅是给操作系统的调度建议
  • 不同平台对优先级的处理可能不同
  • 过度依赖优先级可能导致线程饥饿

线程同步与临界区

当多个线程并发访问共享资源时,可能产生竞态条件。需要通过同步机制保护临界区

// 同步方法
public synchronized void criticalMethod() {
    // 临界区代码
}

// 同步代码块
public void criticalSection() {
    synchronized(this) {
        // 临界区代码
    }
}

同步规则:

  1. 同一时刻只能有一个线程执行对象的同步实例方法
  2. 同一时刻只能有一个线程执行类的同步静态方法
  3. 同步会带来性能开销,应尽量减小同步范围

线程状态转换

Java线程生命周期包含6种状态(通过Thread.getState()获取):

  1. 新建(NEW):线程对象已创建但未启动
  2. 可运行(RUNNABLE):线程正在执行或等待CPU资源
  3. 阻塞(BLOCKED):等待获取监视器锁
  4. 等待(WAITING):无限期等待其他线程显式唤醒
  5. 限时等待(TIMED_WAITING):在指定时间内等待
  6. 终止(TERMINATED):线程执行完毕
Thread thread = new Thread(() -> {
    try {
        Thread.sleep(1000); // 进入TIMED_WAITING状态
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
System.out.println(thread.getState()); // NEW
thread.start();
System.out.println(thread.getState()); // RUNNABLE

线程控制操作

线程支持以下控制操作,但需谨慎使用:

  • interrupt():请求中断线程(需配合中断检查使用)
  • stop():强制终止线程(已废弃,不安全)
  • suspend()/resume():挂起/恢复线程(已废弃)

正确的中断处理方式:

public void run() {
    while(!Thread.currentThread().isInterrupted()) {
        // 正常执行逻辑
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // 恢复中断状态
            Thread.currentThread().interrupt();
            break;
        }
    }
}

并发工具类

Java提供多种高级并发工具简化开发:

  1. 原子变量AtomicInteger等,保证单个变量操作的原子性
  2. 显式锁ReentrantLock提供比synchronized更灵活的锁机制
  3. 执行器框架ExecutorService管理线程池和任务调度
  4. Fork/Join框架:适用于可分治的任务处理
  5. 线程局部变量ThreadLocal为每个线程维护独立变量副本
// 原子变量示例
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();

// 线程局部变量示例
ThreadLocal dateFormat = 
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

这些工具类在保持线程安全的同时,提供了比基础同步机制更高的性能和灵活性。

同步机制与原子操作

原子变量实现无锁线程安全

原子变量(Atomic Variables)通过CAS(Compare-And-Swap)机制实现无锁线程安全操作,适用于计数器、标志位等场景。Java原子包(java.util.concurrent.atomic)提供多种原子类型:

// 原子整型示例
AtomicInteger atomicInt = new AtomicInteger(0);
int newValue = atomicInt.incrementAndGet(); // 原子递增

// 原子引用示例
AtomicReference atomicRef = new AtomicReference<>("initial");
atomicRef.compareAndSet("initial", "updated"); // CAS操作

关键特性:

  • 避免显式同步带来的性能损耗
  • 提供getAndUpdateaccumulateAndGet等复合操作
  • 适合读多写少的高并发场景

显式锁的高级控制

显式锁(Explicit Locks)通过Lock接口提供比synchronized更精细的控制:

ReentrantLock lock = new ReentrantLock();
try {
    lock.lock();  // 可定时、可中断的获取锁
    // 临界区代码
} finally {
    lock.unlock(); // 必须显式释放
}

核心优势:

  1. 尝试获取锁:tryLock()支持立即返回或超时等待
  2. 公平性选择:构造时可指定公平/非公平策略
  3. 条件变量:通过newCondition()实现精细线程通信

执行器框架任务调度

执行器框架(Executor Framework)解耦任务提交与执行:

ExecutorService executor = Executors.newFixedThreadPool(4);
Future future = executor.submit(() -> {
    TimeUnit.SECONDS.sleep(1);
    return 42;
});

// 获取异步结果
Integer result = future.get(); 
executor.shutdown();

主要组件:

  • ThreadPoolExecutor:可配置的核心/最大线程数
  • ScheduledExecutorService:支持延迟/周期性任务
  • Future:获取异步计算结果

Fork/Join并行处理

fork/join框架采用工作窃取(work-stealing)算法处理可分解任务:

class FibonacciTask extends RecursiveTask {
    final int n;
    FibonacciTask(int n) { this.n = n; }
    
    protected Integer compute() {
        if (n <= 1) return n;
        FibonacciTask f1 = new FibonacciTask(n - 1);
        f1.fork();  // 异步执行子任务
        return new FibonacciTask(n - 2).compute() + f1.join();
    }
}

ForkJoinPool pool = new ForkJoinPool();
int result = pool.invoke(new FibonacciTask(10));

最佳实践:

  • 任务应分解为独立子任务
  • 避免IO阻塞操作
  • 子任务规模应足够大以抵消调度开销

线程局部存储

ThreadLocal实现线程隔离的变量存储:

ThreadLocal dateFormat = 
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

// 每个线程获取独立实例
String date = dateFormat.get().format(new Date()); 

注意事项:

  • 使用后需调用remove()防止内存泄漏
  • 不适合存储大量数据
  • InheritableThreadLocal支持父子线程值继承

同步器对比选型

同步机制适用场景特性
原子变量计数器、状态标志无锁、高性能
显式锁复杂同步逻辑可中断、超时控制
线程池任务并行处理资源复用、负载均衡
Fork/Join可分治问题工作窃取、自动并行化

这些并发工具通过不同的抽象层次,为开发者提供了从底层原子操作到高级任务调度的完整解决方案。

信号量(Semaphore)应用

信号量基础原理

信号量是一种控制多线程并发访问资源的同步器,与synchronized单线程限制不同,它允许N个线程同时访问资源(N可为任意正整数)。核心机制通过虚拟许可(permit)实现:

// 创建包含3个许可的信号量
Semaphore semaphore = new Semaphore(3);

当N=1时,信号量可模拟synchronized的互斥访问特性。信号量维护的许可数量决定了可同时访问资源的线程数。

关键操作方法

  • acquire():获取许可(若无可用许可则阻塞)
  • release():释放许可(增加可用许可数)
  • tryAcquire():尝试获取许可(立即返回成功/失败)
  • availablePermits():查询当前可用许可数
// 典型使用模式
semaphore.acquire();
try {
    // 访问受保护资源
} finally {
    semaphore.release();
}

公平性控制

信号量支持公平/非公平两种模式,通过构造器指定:

// 创建公平信号量(保证FIFO获取顺序)
Semaphore fairSemaphore = new Semaphore(3, true);

公平信号量能避免线程饥饿,但会带来额外的性能开销。非公平信号量(默认)通常具有更高的吞吐量。

餐厅餐桌分配案例

以下代码模拟餐厅餐桌管理系统,演示信号量实际应用:

class Restaurant {
    private final Semaphore tables;
    
    public Restaurant(int tableCount) {
        this.tables = new Semaphore(tableCount);
    }
    
    public void getTable(int customerId) throws InterruptedException {
        System.out.println("顾客#" + customerId + "等待餐桌...");
        tables.acquire();
        System.out.println("顾客#" + customerId + "获得餐桌");
    }
    
    public void returnTable(int customerId) {
        System.out.println("顾客#" + customerId + "归还餐桌");
        tables.release();
    }
}

class Customer extends Thread {
    private final Restaurant restaurant;
    private final int id;
    
    public Customer(Restaurant r, int id) {
        this.restaurant = r;
        this.id = id;
    }
    
    @Override
    public void run() {
        try {
            restaurant.getTable(id);
            Thread.sleep((int)(Math.random() * 10000)); // 模拟用餐时间
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            restaurant.returnTable(id);
        }
    }
}

注意事项

  1. 许可释放次数可超过初始数量(通过多次调用release()
  2. 一个线程获取的许可可由其他线程释放
  3. 必须确保release()在finally块中调用,避免资源泄漏
  4. 支持批量获取/释放许可:
    semaphore.acquire(2); // 获取2个许可
    semaphore.release(3); // 释放3个许可
    

信号量特别适用于资源池管理、流量控制等场景,相比synchronized提供更灵活的并发控制能力。

屏障(Barrier)与相位器(Phaser)

CyclicBarrier线程同步机制

CyclicBarrier使一组线程在屏障点(barrier point)同步等待,当最后一个线程到达后,所有线程才会继续执行。与CountDownLatch不同,CyclicBarrier可重复使用,特别适用于迭代算法中的多阶段计算。

// 创建5线程的屏障,并定义屏障动作
Runnable barrierAction = () -> System.out.println("所有线程到达屏障点");
CyclicBarrier barrier = new CyclicBarrier(5, barrierAction);

class Worker extends Thread {
    private final CyclicBarrier barrier;
    
    public Worker(CyclicBarrier barrier) {
        this.barrier = barrier;
    }
    
    @Override
    public void run() {
        try {
            // 执行第一阶段工作
            System.out.println(getName() + "完成阶段1");
            barrier.await();  // 等待其他线程
            
            // 所有线程到达后继续执行
            System.out.println(getName() + "开始阶段2");
        } catch (Exception e) {
            Thread.currentThread().interrupt();
        }
    }
}

关键特性:

  • await()方法返回到达索引(最后到达的线程返回0)
  • 支持超时机制:await(long timeout, TimeUnit unit)
  • 屏障被破坏时抛出BrokenBarrierException
  • 重置功能:reset()方法可重新使用屏障

Phaser动态线程协调

Phaser提供比CyclicBarrier更灵活的同步控制,主要特点包括:

  1. 动态注册/注销:支持运行时调整参与线程数
  2. 分阶段推进:每轮同步后phase值递增(超过Integer.MAX_VALUE后归零)
  3. 分层结构:支持树形phaser结构减少竞争
  4. 可终止性:通过onAdvance()控制终止条件
class Task extends Thread {
    private final Phaser phaser;
    
    public Task(String name, Phaser phaser) {
        super(name);
        this.phaser = phaser;
        phaser.register(); // 动态注册
    }
    
    @Override
    public void run() {
        do {
            // 执行阶段任务
            System.out.println(getName() + "完成阶段"+phaser.getPhase());
            phaser.arriveAndAwaitAdvance();
        } while (!phaser.isTerminated());
    }
}

// 使用示例
Phaser phaser = new Phaser(1); // 初始注册1个控制线程
for(int i=0; i<3; i++) {
    new Task("Worker-"+i, phaser).start();
}
phaser.arriveAndDeregister(); // 控制线程退出

相位器高级功能

自定义相位切换动作

通过继承Phaser并重写onAdvance()方法实现:

class CustomPhaser extends Phaser {
    @Override
    protected boolean onAdvance(int phase, int parties) {
        System.out.println("阶段"+phase+"完成,参与方:"+parties);
        return phase >= 3 || parties == 0; // 超过3阶段或无双注册方时终止
    }
}
多阶段任务协同

Phaser特别适合多阶段并行计算场景:

class ProcessingTask extends Thread {
    private final Phaser phaser;
    private final List data;
    
    public ProcessingTask(Phaser p, List data) {
        this.phaser = p;
        this.data = data;
        p.register();
    }
    
    @Override
    public void run() {
        // 阶段1:数据加载
        loadData();
        phaser.arriveAndAwaitAdvance();
        
        // 阶段2:数据处理
        processData();
        phaser.arriveAndAwaitAdvance();
        
        // 阶段3:结果汇总
        saveResults();
        phaser.arriveAndDeregister();
    }
    // ...具体方法实现...
}

对比选型建议

特性CyclicBarrierPhaser
参与方数量固定动态可调
重用性支持reset()自动推进相位
分层结构不支持支持
终止控制无内置机制通过onAdvance()控制
适用场景简单多线程同步复杂分阶段任务协调

实际开发中,Phaser更适合以下场景:

  1. 需要动态增减参与线程的并行任务
  2. 多阶段流水线处理系统
  3. 需要精细控制相位切换逻辑的应用
  4. 大规模并行计算中的任务协调

注意事项:

  • 避免在phaser未终止时长时间阻塞
  • 确保正确匹配register()和arriveAndDeregister()调用
  • 在树形结构中合理分配子phaser的负载

闭锁(Latch)与交换器(Exchanger)

CountDownLatch一次性同步

CountDownLatch是一种不可重置的同步辅助类,允许一个或多个线程等待直到在其他线程中执行的一组操作完成。典型应用场景包括:

  • 服务启动依赖:确保所有前置服务就绪后再启动主服务
  • 并行任务初始化:等待所有工作线程完成初始化后再处理数据
// 创建初始计数为2的闭锁
CountDownLatch latch = new CountDownLatch(2);

class Service extends Thread {
    private final CountDownLatch latch;
    
    public Service(String name, CountDownLatch latch) {
        super(name);
        this.latch = latch;
    }
    
    @Override
    public void run() {
        try {
            System.out.println(getName() + "正在启动...");
            Thread.sleep((int)(Math.random() * 3000));
            System.out.println(getName() + "启动完成");
            latch.countDown();  // 计数减1
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

// 主线程等待服务启动
new Service("数据库服务", latch).start();
new Service("缓存服务", latch).start();

latch.await();  // 阻塞直到计数归零
System.out.println("所有依赖服务就绪,开始启动主应用");

关键特性:

  1. 计数不可重置,达到零后所有等待线程立即释放
  2. countDown()可在不同线程调用
  3. 支持超时等待:await(long timeout, TimeUnit unit)

Exchanger线程数据交换

Exchanger实现两个线程间的双向数据交换屏障,适用于生产者-消费者模式中的缓冲区交换:

Exchanger> exchanger = new Exchanger<>();

class Producer extends Thread {
    private List buffer = new ArrayList<>();
    private final Exchanger> exchanger;
    
    public Producer(Exchanger> exchanger) {
        this.exchanger = exchanger;
    }
    
    @Override
    public void run() {
        while (!Thread.interrupted()) {
            // 生产数据
            for (int i = 0; i < 5; i++) {
                buffer.add(i);
            }
            System.out.println("生产者已填充缓冲区");
            
            try {
                // 交换缓冲区
                buffer = exchanger.exchange(buffer);
                Thread.sleep(1000);  // 模拟处理延迟
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

class Consumer extends Thread {
    private List buffer = new ArrayList<>();
    private final Exchanger> exchanger;
    
    public Consumer(Exchanger> exchanger) {
        this.exchanger = exchanger;
    }
    
    @Override
    public void run() {
        while (!Thread.interrupted()) {
            try {
                // 获取生产者数据
                buffer = exchanger.exchange(buffer);
                System.out.println("消费者收到数据量: " + buffer.size());
                
                // 清空缓冲区返回给生产者
                buffer.clear();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }
}

实现要点:

  1. 交换操作是同步的,双方线程会互相阻塞直到都到达交换点
  2. 支持泛型数据交换,但类型必须匹配
  3. 可配置超时机制避免永久等待

同步器对比

特性CountDownLatchExchanger
参与方数量多对一(等待计数归零)严格一对一
重用性不可重置可重复交换
数据传递支持双向数据交换
典型应用场景服务启动依赖生产者-消费者缓冲区交换

注意事项:

  1. CountDownLatch的countDown()应放在finally块确保执行
  2. Exchanger交换的数据对象应是线程安全的
  3. 两者都不支持中断的线程恢复操作,需自行处理中断状态

Java多线程编程总结

完整工具链与适用场景

Java提供了从基础线程管理到高级同步器的完整多线程编程工具链。不同同步器针对特定场景设计:

  • 信号量(Semaphore):精确控制资源并发访问量
  • 屏障(CyclicBarrier):协调多阶段并行任务
  • 闭锁(CountDownLatch):处理服务启动等一次性依赖
  • 交换器(Exchanger):实现线程间安全数据交换
// 典型同步器使用模式对比
Semaphore semaphore = new Semaphore(10); // 限流10并发
CyclicBarrier barrier = new CyclicBarrier(5); // 5线程同步点
CountDownLatch latch = new CountDownLatch(3); // 3次计数
Exchanger exchanger = new Exchanger<>(); // 数据交换

线程安全核心考量

实现线程安全需要根据场景合理选择方案:

  1. 原子变量:适用于简单计数器等场景
    AtomicLong counter = new AtomicLong(0);
    counter.getAndIncrement();
    
  2. 显式锁:复杂同步逻辑时提供更精细控制
    Lock lock = new ReentrantLock();
    lock.lockInterruptibly(); // 可中断获取锁
    
  3. 线程封闭:通过ThreadLocal实现线程隔离
    ThreadLocal connHolder = ThreadLocal.withInitial(
        () -> DriverManager.getConnection(DB_URL));
    

线程状态管理

理解线程状态转换对问题诊断至关重要:

状态触发条件转换方法
NEW线程刚创建Thread.start()
RUNNABLE可执行状态获取CPU时间片
BLOCKED等待监视器锁synchronized块竞争
WAITING无限期等待Object.wait()/join()
TIMED_WAITING有限期等待Thread.sleep(n)
TERMINATED执行完毕run()方法结束
// 线程状态监控示例
Thread thread = new Thread(task);
System.out.println(thread.getState()); // NEW
thread.start();
System.out.println(thread.getState()); // RUNNABLE

现代并发框架优势

Java 5+引入的并发框架大幅简化了复杂并行任务实现:

  1. ExecutorService:线程池统一管理
    ExecutorService exec = Executors.newWorkStealingPool();
    Future future = exec.submit(() -> 42);
    
  2. ForkJoinPool:自动任务分解与工作窃取
    class SumTask extends RecursiveTask {
        protected Long compute() {
            // 任务分解逻辑
        }
    }
    
  3. CompletableFuture:异步编程组合
    CompletableFuture.supplyAsync(() -> "data")
        .thenApply(String::toUpperCase)
        .thenAccept(System.out::println);
    

最佳实践建议

  1. 优先使用高层抽象(如并发集合、Executor)
  2. 避免过度同步,减小临界区范围
  3. 使用线程池代替直接创建线程
  4. 注意死锁、活锁和资源饥饿问题
  5. 多使用不可变对象和线程安全容器
// 线程安全集合示例
ConcurrentMap map = new ConcurrentHashMap<>();
map.compute("key", (k,v) -> v == null ? 1 : v+1);

// 不可变对象示例
final class ImmutablePoint {
    private final int x, y;
    // 构造后状态不可变
}

Java多线程体系既提供了底层控制能力,又通过高级抽象简化了并发编程。开发者应当根据具体需求选择合适的并发工具,在保证线程安全的前提下优化性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

面朝大海,春不暖,花不开

您的鼓励是我最大的创造动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值