简体   繁体   English

如何在可序列化 class 中正确使用“锁定”变量进行同步?

[英]How to properly use a "lock" variable for synchronization in a Serializable class?

I am trying to figure out how to avoid an NPE in the the code below.我试图弄清楚如何在下面的代码中避免 NPE。 I am unable to purposely reproduce the error, but every once in a while I get an NPE at the line 40 synchronized(lock) { .我无法故意重现该错误,但每隔一段时间我就会在第 40 行synchronized(lock) {处得到一个 NPE。 My guess is that it's happening after a serialization/deserialization process - but that is just a guess.我的猜测是它发生在序列化/反序列化过程之后 - 但这只是一个猜测。

My IDE gives me a compile "tip" that says synchronization on a non-final variable (lock) , but to be quite honest, I'm not as familiar with synchronized code blocks and how a serializable class affects final variables.我的 IDE 给了我一个编译“提示”,上面写着synchronization on a non-final variable (lock) ,但老实说,我对synchronized代码块以及可序列化的 class 如何影响final变量并不熟悉。

As an FYI, the code below was copied/modified from a Struts Wiki: https://cwiki.apache.org/confluence/display/WW/HibernateAndSpringEnabledExecuteAndWaitInterceptor (towards the bottom of the page in the comments).作为一个仅供参考,下面的代码是从 Struts Wiki 复制/修改的: https://cwiki.apache.org/confluence/display/WW/HibernateAndSpringEnabledExecuteAndWaitInterceptor (朝向页面底部的注释)。

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();
    }
}

The code in the question and the linked wiki page has an unresolvable data race condition.问题中的代码和链接的 wiki 页面存在无法解决的数据竞争条件。 Unfortunately, the constructor of BackgroundProcess allows a reference to this to escape the constructor by starting a new thread that calls beforeInvocation .不幸的是, BackgroundProcess的构造函数允许对this的引用通过启动一个调用beforeInvocation的新线程来逃避构造函数 Because this call can occur before the constructor of the child class completes, it is not safe to extend BackgroundProcess .由于此调用可能发生在子 class 的构造函数完成之前,因此扩展BackgroundProcess是不安全的。

This code attempts to handle the race condition on entityManagerFactory through use of the lock object, initializationComplete flag, and wait / notify usage.此代码尝试通过使用lock object、 initializationComplete标志和wait / notify用法来处理entityManagerFactory上的竞争条件。 However, this only moves the race condition from entityManagerFactory to lock , as Java only initialises the fields of subclasses after the parent class constructor completes .但是,这只会将竞争条件从entityManagerFactory移动到lock ,因为 Java 仅在父 class 构造函数完成初始化子类的字段 This is true regardless of whether the field is initialised inline or in the constructor.无论该字段是内联初始化还是在构造函数中初始化,这都是正确的。

You can find more details about this problem in the excellent accepted answer to the question Java: reference escape .您可以在对问题Java: reference escape的出色接受答案中找到有关此问题的更多详细信息。

My best advice is to avoid the use of BackgroundProcess and find another way to solve the original problem.我最好的建议是避免使用BackgroundProcess并找到解决原始问题的另一种方法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM