问题
In my Action Bean I load Entities from a database, use data from those Entities to create new EntityObjects using Java 8 ParallelStream, and store those EntityObjects in a List for later use on a web page.
I use the following to create these Objects using the Hibernate mapped Entities:
List<Entity> entities = dao.getEntities();
List<Object> entityObjects = new ArrayList<>();
entityObjects.addAll(
entities.parallelStream()
.map(EntityObject::new)
.collect(Collectors.toList())
);
with a EntityObject constructor looking like:
public EntityObject(Entity entity) {...}
When Trying to load the page using the Action Bean I get Hibernate Exceptions. They are different every time I try to load the page, but all have to do with Shared References, such as:
... ERROR: Found shared references to a collection
and
... ERROR: A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance
What am I doing wrong?
EDIT: Fixed the code.
回答1:
parallelStream() will execute the code on multiple threads (good for performance). However, the Hibernate Session object is not thread-safe and using it in a threaded fashion is likely to fail in mysterious ways. The other answer from Bryan Pugh regarding transactions is similar: in most cases the transaction is held in a ThreadLocal variable. Executing parallelStream() means some code runs on a different (unrelated) thread. This can be easily shown using this code:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class TestParallel {
public static void main(String[] args) {
// Keep a set of thread-ids that were used. Make sure this is thread-safe.
Set<String> threadId = Collections.synchronizedSet(new HashSet<String>());
// Create a long enough list such that spliterator will indeed use multiple threads.
// If the collection is very small (1 or 2 entries) no multithreading will be done.
Collection<Integer> numbers = new ArrayList<>();
for(int i=0; i<10000; i++) {
numbers.add(i);
}
// Now for each number in the list, see which thread is handling it by dumping the thread-name.
numbers.parallelStream().forEach(n -> threadId.add(Thread.currentThread().toString()));
// And give a summary of the total number of threads that were used in the parallelStream()
System.out.println("Used threads: " + threadId.size());
}
}
When using a small list (change 10000 to 2) one thread will be used. With the given constant on my machine 12 threads are used. Depending on the number of entries in your database and the performance and JDK version you used, the numbers will vary but in general: avoid parallelStream() unless you know what it does.
回答2:
Use
stream()
instead of
parallelStream()
.
I'm not quite sure how it works, but when using parallelStream() you operate on several objects at once. This can cause you to have different references to the same collection at one moment in time, and this is under specific circumstances not allowed by Hibernate, causing the exception.
回答3:
I also don't have a definitive answer, but I have noticed that this seems to stem from the fact that you sometimes/always loose your transaction when you parallelize. It seems that different threads can't/don't have access the transaction of its parent.
Consider: Spring transaction manager and multithreading
https://dzone.com/articles/spring-transaction-management-over-multiple-thread-1
I believe this is why.
来源:https://stackoverflow.com/questions/37143082/random-hibernate-exception-when-using-java-8-parallelstream-in-action-bean