Grails中的多线程 - 将域对象传递到每个线程会导致某些字段随机为空

时间:2017-03-09 17:21:28

标签: java multithreading grails groovy threadpool

我试图通过引入并行编程来加速Grails应用程序中的进程。这个特殊的过程需要筛选数千个文档,从中收集必要的数据并将其导出到excel文件中。

经过几个小时的尝试追踪为什么这个过程如此缓慢,我已经确定该过程必须做很多工作,从每个域对象收集特定的数据部分。 (示例:域对象中包含数据列表,此过程将获取这些列表中的每个索引,并将其附加到带逗号的字符串中,以便在Excel工作表的单元格中生成外观漂亮的排序列表。还有更多示例但那些不应该是重要的。)

因此,任何不是简单数据访问(document.id,document.name等等)都会导致此过程花费很长时间。

我的想法是使用每个文档的线程来异步获取所有这些数据,当每个线程收集完数据后,它可以返回主线程并放入excel表,现在所有这些都具有简单的数据访问权限,因为线程已经收集了所有数据。

这似乎有效,但我有域对象和线程的错误。每个线程都在其相应的文档域对象中传递,但无论出于何种原因,文档域对象将随机将其部分数据更改为null。

例如:在将文档传递到线程之前,域对象的一部分将具有如下所示的列表:[美国,英格兰,威尔士],在任何点随机,列表将如下所示线程:[美国,null,威尔士]。这种情况发生在域对象的任何随机部分,在任何随机时间。

生成线程:

def docThreadPool = Executors.newFixedThreadPool(1)
def docThreadsResults = new Future<Map>[filteredDocs.size()]
filteredDocs.each {
    def final document = it
    def future = docThreadPool.submit(new DocumentExportCallable(document))
    docThreadsResults[docCount] = future
    docCount++
}

从线程中获取数据:

filteredDocs.each {
        def data = docThreadsResults[count].get()

        build excel spreadsheet...
}

DocumentExportCallable类:

class DocumentExportCallable implements Callable {
    def final document

    DocumentExportCallable(document) {
        this.document = document
    }

    Map call() {
            def data = [:]

            code to get all the data...

            return data
    }
}

编辑: 如下所示,如果我可以向您显示域对象,那将非常有用。但是我无法做到这一点。但是,你们问我关于域名对象的事实让我觉得它可能就是问题所在。事实证明,在线程中随机混乱的域对象的每个部分都是“映射”内的域对象中的变量,它使用SQL连接来获取这些变量的数据。我刚刚意识到在Grails中懒惰与渴望获取。我想知道这可能是问题所在......默认情况下,它被设置为延迟提取,因此每个线程对db的持续访问可能是出错的地方。我相信找到一种方法将其改为渴望获取可能会解决问题。

2 个答案:

答案 0 :(得分:1)

我得到了为什么这些空值随机出现的答案。现在一切似乎都在运行,我的实现现在比以前的实现快得多!

事实证明,即使在获得对象本身之后,当您访问这些字段时,我也不知道具有1米关系的Grails域对象会进行单独的sql调用。这必然导致这些线程进行非线程安全的sql调用,这些调用创建了这些随机空值。在这种特定情况下设置这些1-m属性以便急切地解决问题。

对于稍后阅读的人来说,你会想要阅读懒惰与渴望获取以获得更好的理解。

至于代码:

这些是我的域对象中的1-m变量:

static hasMany = [propertyOne : OtherDomainObject, propertyTwo : OtherDomainObject, propertyThree : OtherDomainObject]

我在我的数据库调用中添加了一个标志,该标志将为此特定情况启用此代码,因为我不希望在整个应用程序中始终急切地获取这些属性:

if (isEager) {
    fetchMode 'propertyOne', FetchMode.JOIN
    fetchMode 'propertyTwo', FetchMode.JOIN
    fetchMode 'propertyThree', FetchMode.JOIN
    setResultTransformer Criteria.DISTINCT_ROOT_ENTITY
}

我道歉,但此刻我不记得为什么我要把#34; setResultTransformer&#34;在上面的代码中,但没有它有问题。也许有人后来可以解释这个,否则我确定谷歌搜索会解释。

答案 1 :(得分:0)

发生的事情是你的grails域对象从hibernate会话中分离,因此当你的线程试图加载延迟属性时会遇到LazyInitiationException。

切换到急切的提取对你有用,但它可能不是每个人的选择。您还可以使用grails异步任务框架,而不是内置会话处理。见https://async.grails.org/latest/guide/index.html

但是,即使使用grails异步任务在线程之间传递对象,似乎也会将其分离,因为新线程将具有新绑定的会话。我在新线程上找到.merge()Document.get(id)的解决方案,将其与调用线程上的会话绑定。

我认为最佳解决方案是让hibernate在新线程上加载对象,这意味着在您的代码段中,您将在会话支持的线程上传递文档ID和@PostMapping(value=("/addMessage")) public String addMessagePostPicture(@RequestParam("picture") MultipartFile picture, Map<String, Object> model, HttpServletRequest request, @ModelAttribute MessageForm message) { String[] contacts = request.getParameterValues("contacts"); if(message.getContacts() == null){ System.out.println("yes"); message.setContact(contacts); } public class MessageForm { private String[] contacts; public String[] getContacts() { return contacts; } public void setContact(String[] contacts) { this.contacts = contacts; } }