【问题标题】:How to properly use a "lock" variable for synchronization in a Serializable class?如何在 Serializable 类中正确使用“lock”变量进行同步?
【发布时间】:2022-09-27 10:45:07
【问题描述】:

我试图弄清楚如何在下面的代码中避免 NPE。我无法故意重现该错误,但每隔一段时间我就会在第 40 行 synchronized(lock) { 处获得 NPE。我的猜测是它发生在序列化/反序列化过程之后 - 但这只是一个猜测。

我的 IDE 给了我一个编译“提示”,上面写着synchronization on a non-final variable (lock),但老实说,我对synchronized 代码块以及可序列化类如何影响final 变量并不熟悉。

作为仅供参考,以下代码是从 Struts Wiki 复制/修改的:https://cwiki.apache.org/confluence/display/WW/HibernateAndSpringEnabledExecuteAndWaitInterceptor(朝向 cmets 页面的底部)。

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;

public class OpenSessionBackgroundProcess extends BackgroundProcess implements Serializable {

    private static final long serialVersionUID = 3884464561311686443L;

    private final transient EntityManagerFactory entityManagerFactory;

    // used for synchronization
    protected boolean initializationComplete;
    private transient Object lock = new Object();

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

    protected void beforeInvocation() throws Exception {
        while (!initializationComplete) {
            try {
                synchronized (lock) {  // <----- NPE HERE
                    lock.wait(100);
                }
            } catch (InterruptedException e) {
                // behavior ignores cause of re-awakening.
            }
        }
        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());
    }

    /**
     * Override default readObject() method when deserializing
     *
     * @param serialized the serialized object
     */
    private void readObject(ObjectInputStream serialized) throws IOException, ClassNotFoundException {
        serialized.defaultReadObject();
        lock = new Object();
    }
}
  • 为什么它需要可序列化?

标签: java deserialization synchronized


【解决方案1】:

问题中的代码和链接的 wiki 页面存在无法解决的数据竞争条件。不幸的是,BackgroundProcess 的构造函数允许通过启动一个调用beforeInvocation 的新线程来引用thisescape the constructor。因为这个调用可能发生在子类的构造函数完成之前,扩展BackgroundProcess 是不安全的。

此代码尝试通过使用lock 对象、initializationComplete 标志和wait/notify 用法来处理entityManagerFactory 上的竞争条件。但是,这只会将竞态条件从entityManagerFactory 移动到lock,因为Java 仅initialises the fields of subclasses after the parent class constructor completes。无论该字段是内联初始化还是在构造函数中初始化,这都是正确的。

您可以在对问题Java: reference escape 的出色接受答案中找到有关此问题的更多详细信息。

我最好的建议是避免使用BackgroundProcess 并找到解决原始问题的另一种方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-07-03
    • 2011-11-06
    • 1970-01-01
    • 2021-12-01
    • 1970-01-01
    • 2020-09-05
    • 2017-01-31
    • 2011-08-25
    相关资源
    最近更新 更多