You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Serializable类中正确使用lock变量同步并避免NPE

问题分析与解决方案

问题根源

1. 空指针异常(NPE)的直接原因

lock是transient修饰的变量,序列化时会被忽略,反序列化后默认值为null。虽然重写了readObject方法重新初始化lock,但反序列化过程中,readObject的执行时机晚于对象实例的创建——如果在readObject完成前,有线程调用beforeInvocation方法,就会访问到null的lock,触发NPE。

2. IDE警告的原因

synchronization on a non-final variable的警告是因为非final的锁变量可能在同步块执行过程中被替换,导致不同线程使用不同的锁对象,完全失去同步的作用,破坏线程安全。

修复方案

步骤1:确保锁对象不可变且初始化安全

将lock声明为final transient,保证锁对象一旦初始化就不会被替换,同时避免序列化:

private final transient Object lock;

然后在构造器中完成初始化:

public OpenSessionBackgroundProcess(String name, ActionInvocation invocation, int threadPriority, EntityManagerFactory entityManagerFactory) {
    super(name, invocation, threadPriority);
    this.entityManagerFactory = entityManagerFactory;
    this.lock = new Object(); // 初始化final锁
    initializationComplete = true;
    synchronized (lock) {
        lock.notify();
    }
}

步骤2:修复反序列化时final transient变量的初始化

因为final transient变量不会被自动反序列化,需要通过反射在readObject中初始化:

private void readObject(ObjectInputStream serialized) throws IOException, ClassNotFoundException {
    serialized.defaultReadObject();
    // 通过反射设置final的lock变量
    try {
        Field lockField = OpenSessionBackgroundProcess.class.getDeclaredField("lock");
        lockField.setAccessible(true);
        lockField.set(this, new Object());
    } catch (NoSuchFieldException | IllegalAccessException e) {
        throw new RuntimeException("Failed to initialize lock during deserialization", e);
    }
}

步骤3:保证共享变量的可见性

initializationComplete是多线程共享的变量,添加volatile修饰,确保线程间的可见性,避免出现线程一直等待的情况:

protected volatile boolean initializationComplete;

完整修复后的代码

import com.opensymphony.xwork2.ActionInvocation;
import org.apache.struts2.interceptor.BackgroundProcess;
import org.springframework.orm.jpa.EntityManagerFactoryUtils;
import org.springframework.orm.jpa.EntityManagerHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.lang.reflect.Field;

public class OpenSessionBackgroundProcess extends BackgroundProcess implements Serializable {

    private static final long serialVersionUID = 3884464561311686443L;

    private final transient EntityManagerFactory entityManagerFactory;

    // 添加volatile保证多线程可见性
    protected volatile boolean initializationComplete;
    // 声明为final transient,确保锁对象不可变
    private final transient Object lock;

    public OpenSessionBackgroundProcess(String name, ActionInvocation invocation, int threadPriority, EntityManagerFactory entityManagerFactory) {
        super(name, invocation, threadPriority);
        this.entityManagerFactory = entityManagerFactory;
        this.lock = new Object();
        initializationComplete = true;
        synchronized (lock) {
            lock.notify();
        }
    }

    protected void beforeInvocation() throws Exception {
        while (!initializationComplete) {
            try {
                synchronized (lock) {
                    lock.wait(100);
                }
            } catch (InterruptedException e) {
                // 忽略中断,继续等待
            }
        }
        EntityManager em = entityManagerFactory.createEntityManager();
        TransactionSynchronizationManager.bindResource(entityManagerFactory, new EntityManagerHolder(em));
        super.beforeInvocation();
    }

    protected void afterInvocation() throws Exception {
        super.afterInvocation();
        EntityManagerHolder emHolder = (EntityManagerHolder)
                TransactionSynchronizationManager.unbindResource(entityManagerFactory);
        EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager());
    }

    private void readObject(ObjectInputStream serialized) throws IOException, ClassNotFoundException {
        serialized.defaultReadObject();
        // 通过反射初始化final的lock变量
        try {
            Field lockField = OpenSessionBackgroundProcess.class.getDeclaredField("lock");
            lockField.setAccessible(true);
            lockField.set(this, new Object());
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new RuntimeException("Failed to initialize lock during deserialization", e);
        }
    }
}

额外说明

  • final锁变量确保了同步块始终使用同一个锁对象,避免了锁对象被替换的风险,解决了IDE的警告。
  • volatile修饰initializationComplete,保证了线程间的可见性,避免线程因看不到最新值而无限等待。
  • 反射初始化final变量是Java中处理final transient变量反序列化的常用手段,确保反序列化后的对象拥有有效的锁对象。

内容的提问来源于stack exchange,提问作者Burton

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 03:40:33