Java同步处理流程如何规范

wen java案例 30

本文目录导读:

Java同步处理流程如何规范

  1. 核心设计原则(先于代码)
  2. 标准同步处理流程(以 synchronized 为例)
  3. 使用 ReentrantLock 的规范流程(更灵活的场景)
  4. 关于“流程”的进阶规范:等待-通知模式 (Wait/Notify)
  5. 总结:规范同步流程的“清单”
  6. 例外情况处理

在Java中,规范处理同步(Synchronization)是保证多线程安全、避免死锁和性能瓶颈的核心,这里的“同步处理流程”通常指使用锁(如 synchronizedReentrantLock)或原子类管理共享资源访问的模式

下面我将从设计原则、标准编码流程、常见陷阱及规范几个方面为你拆解。

核心设计原则(先于代码)

在写同步代码前,先思考以下3点:

  1. 最小化锁的范围:只锁必要的代码块,不要锁整个方法(尤其是耗时操作)。
  2. 明确锁的对象:锁的是this、类对象Class、还是某个专门的final锁对象?确保多个线程看到的是同一把锁
  3. 避免死锁:如果存在多个锁,保证所有线程获取锁的顺序一致

标准同步处理流程(以 synchronized 为例)

synchronized 是Java最基础的同步机制,流程最典型,规范写法如下:

明确共享资源(要保护的数据)

  • 确定哪些变量/对象会被多个线程同时读写。
  • 规范:将这些字段标记为 private,并添加 final(如果引用不变)。

选择合适的锁对象

  • 实例方法同步:锁是 this(当前实例),适合保护实例级别的状态。
  • 静态方法同步:锁是 ClassName.class,适合保护类级别的状态。
  • 代码块同步:最推荐,可以指定任意对象作为锁。
    • 规范:锁对象应声明为 private final,防止外部访问或篡改。

编写同步代码块(核心流程)

标准模板

public class Counter {
    // 1. 共享资源(私有)
    private int count = 0;
    // 2. 创建一个私有的、最终的锁对象(推荐)
    private final Object lock = new Object();
    // 3. 所有需要同步访问共享资源的方法,都使用同一个lock
    public void increment() {
        synchronized (lock) {   // 只锁关键操作
            count++;
            // 可以在这里添加复杂逻辑,但要保持块尽量小
        } // 锁自动释放
    }
    public int getCount() {
        synchronized (lock) {
            return count;
        }
    }
}

对比错误写法

// 错误:直接同步整个方法,并且锁的是this,可能被外部篡改
public synchronized void increment() { 
    // 如果这个方法里还有耗时操作(如IO),锁会占用很久,降低性能
    count++;
    // 还有一些数据库操作...
}

使用 ReentrantLock 的规范流程(更灵活的场景)

相比于 synchronizedReentrantLock 支持超时、可中断、公平锁等,流程规范如下:

import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
public class LockExample {
    private int count = 0;
    private final ReentrantLock lock = new ReentrantLock(); // 非公平锁性能更好
    public void safeIncrement() {
        // 1. 获取锁(建议用try...finally保证释放)
        lock.lock();
        try {
            count++; // 临界区
        } finally {
            lock.unlock(); // 2. 必须释放!即使发生异常
        }
    }
    // 更健壮的写法:带超时,避免无限等待
    public boolean tryIncrementWithTimeout() {
        boolean acquired = false;
        try {
            // 尝试最多等待100ms获取锁
            acquired = lock.tryLock(100, TimeUnit.MILLISECONDS);
            if (acquired) {
                count++;
                return true;
            } else {
                // 获取锁失败,可以做其他事情或者返回错误
                System.out.println("获取锁超时,执行降级逻辑");
                return false;
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 恢复中断状态
            return false;
        } finally {
            if (acquired) {
                lock.unlock(); // 只在获得锁时释放
            }
        }
    }
}

流程”的进阶规范:等待-通知模式 (Wait/Notify)

当一个线程需要满足特定条件(如队列非空)才能继续执行时,规范的同步流程是在循环中等待

public class BlockingQueue {
    private final Object lock = new Object();
    private final List<Object> queue = new LinkedList<>();
    private final int capacity;
    public void put(Object item) throws InterruptedException {
        synchronized (lock) {
            // 1. 必须用while,不能用if,防止虚假唤醒
            while (queue.size() == capacity) {
                lock.wait();
            }
            queue.add(item);
            lock.notifyAll(); // 唤醒所有等待的线程
        }
    }
    public Object take() throws InterruptedException {
        synchronized (lock) {
            while (queue.isEmpty()) { // 再次检查条件
                lock.wait();
            }
            Object item = queue.remove(0);
            lock.notifyAll();
            return item;
        }
    }
}

规范要点

  • 必须在 synchronized 块内调用 wait() / notify()
  • 条件检查用 while 而非 if

规范同步流程的“清单”

步骤 规范要求 反例 / 陷阱
识别临界区 仅保护会变的共享数据,不要锁方法内部的局部变量 锁了整个方法,包括不被共享的局部变量
选择锁对象 使用 private final Object lockthis / ClassName.class 并明确其范围 使用字符串常量或可能被修改的对象作为锁
获取锁 尽量缩小作用域(synchronized 块),减少锁持有时间 在同步块内进行磁盘IO、网络请求等耗时操作
访问共享资源 在临界区内完成所有复合操作(如 i++ 的读取+写入) 在临界区外读取,临界区内写入(导致原子性破坏)
释放锁 ReentrantLock 必须放在 finally 块中释放 lock.lock() 后不写 finally { unlock() }
多锁顺序 所有需要加多个锁的线程,按固定顺序加锁(如按A->B) 线程1锁A再锁B,线程2锁B再锁A(死锁)
条件等待 使用 while(condition) { wait() } 的模式 使用 if(condition) { wait() }
优先使用高级工具 能用 ConcurrentHashMapAtomicIntegerCopyOnWriteArrayList 等,就不要自己手写锁 “重复造轮子”,性能差且易出错

例外情况处理

  1. 线程中断:当你在同步块内调用 InterruptedException 时,规范做法是 Thread.currentThread().interrupt() 恢复中断状态,而不是吞掉异常。
  2. 锁的可见性synchronizedLock 都保证了进入锁时刷新变量,退出锁时写回。如果不使用同步机制,普通变量可能永远看不到其他线程的修改
  3. 异常处理:如果在临界区内抛出非受检异常(如 NullPointerException),“锁”会自动释放(synchronized)或需要在 finally 中释放(ReentrantLock),务必保证异常情况下锁也能释放。

最后建议:在编写你自己的同步逻辑之前,先看看java.util.concurrent包中是否有现成的工具(SemaphoreCountDownLatch、线程安全的集合等),很多时候,不需要自己手写复杂流程,用官方封装好的并发组件更安全、更高效。

抱歉,评论功能暂时关闭!