I am reading Java Concurrency in Practice and got stuck on this program,where author say it won't cause the deadlock. But if I swap the method arguments then it will cause deadlock.
Obj1 = hash-code =1
Obj2 = hash-code =2
Thread1. transfer(Obj1, Obj2) will get lock on fromAcct and will wait for toAcct lock
Thread2.transfer(Obj2, Obj1) will get lock on toAcct and will wait for fromAcct lock
So basically we ended-up in dead-lock.
My question is how come is the dead-lock is avoided in following code.
public class InduceLockOrder {
private static final Object tieLock = new Object();
public void transferMoney(final Account fromAcct,
final Account toAcct,
final DollarAmount amount)
throws InsufficientFundsException {
class Helper {
public void transfer() throws InsufficientFundsException {
if (fromAcct.getBalance().compareTo(amount) < 0)
throw new InsufficientFundsException();
else {
fromAcct.debit(amount);
toAcct.credit(amount);
}
}
}
int fromHash = System.identityHashCode(fromAcct);
int toHash = System.identityHashCode(toAcct);
if (fromHash < toHash) {
synchronized (fromAcct) {
synchronized (toAcct) {
new Helper().transfer();
}
}
} else if (fromHash > toHash) {
synchronized (toAcct) {
synchronized (fromAcct) {
new Helper().transfer();
}
}
} else {
synchronized (tieLock) {
synchronized (fromAcct) {
synchronized (toAcct) {
new Helper().transfer();
}
}
}
}
}
interface DollarAmount extends Comparable<DollarAmount> {
}
interface Account {
void debit(DollarAmount d);
void credit(DollarAmount d);
DollarAmount getBalance();
int getAcctNo();
}
class InsufficientFundsException extends Exception {
}
}
Your example states that it is in fact correct.
Case 1: Thread1.transfer(Obj1, Obj2)
In this case, fromHash < toHash so the first lock is on the fromAcct param or Obj1 and the second is on the toAcct param or Obj2
Case 2: Thread1.transfer(Obj2, Obj1)
In this case, fromHash > toHash so the first lock is on the toAcct param or Obj1 and the second is on the fromAcct param or Obj2
Lock order is same, so no deadlock occurs.
Related
I have this next class:
#Service
public class BusinessService {
#Autowired
private RedisService redisService;
private void count() {
String redisKey = "MyKey";
AtomicInteger counter = null;
if (!redisService.isExist(redisKey))
counter = new AtomicInteger(0);
else
counter = redisService.get(redisKey, AtomicInteger.class);
try {
counter.incrementAndGet();
redisService.set(redisKey, counter, false);
logger.info(String.format("Counter incremented by one. Current counter = %s", counter.get()));
} catch (JsonProcessingException e) {
logger.severe(String.format("Failed to increment counter."));
}
}
// Remaining code
}
and this this my RedisService.java class
#Service
public class RedisService {
private Logger logger = LoggerFactory.getLogger(RedisService.class);
#Autowired
private RedisConfig redisConfig;
#PostConstruct
public void postConstruct() {
try {
String redisURL = redisConfig.getUrl();
logger.info("Connecting to Redis at " + redisURL);
syncCommands = RedisClient.create(redisURL).connect().sync();
} catch (Exception e) {
logger.error("Exception connecting to Redis: " + e.getMessage(), e);
}
}
public boolean isExist(String redisKey) {
return syncCommands.exists(new String[] { redisKey }) == 1 ? true : false;
}
public <T extends Serializable> void set(String key, T object, boolean convertObjectToJson) throws JsonProcessingException {
if (convertObjectToJson)
syncCommands.set(key, writeValueAsString(object));
else
syncCommands.set(key, String.valueOf(object));
}
// Remaining code
}
and this is my test class
#Mock
private RedisService redisService;
#Spy
#InjectMocks
BusinessService businessService = new BusinessService();
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void myTest() throws Exception {
for (int i = 0; i < 50; i++)
Whitebox.invokeMethod(businessService, "count");
// Remaining code
}
my problem is the counter always equals to one in logs when running tests
Counter incremented by one. Current counter = 1(printed 50 times)
and it should print:
Counter incremented by one. Current counter = 1
Counter incremented by one. Current counter = 2
...
...
Counter incremented by one. Current counter = 50
this means the Redis mock always passed as a new instance to BusinessService in each method call inside each loop, so how I can force this behavior to become only one instance used always for Redis inside the test method ??
Note: Above code is just a sample to explain my problem, but it's not a complete code.
Your conclusion that a new RedisService is somehow created in each iteration is wrong.
The problem is that it is a mock object for which you haven’t set any behaviours, so it responds with default values for each method call (null for objects, false for bools, 0 for ints etc).
You need to use Mockito.when to set behaviour on your mocks.
There is some additional complexity caused by the fact that:
you run the loop multiple times, and behaviour of the mocks differ between first and subsequent iterations
you create cached object in method under test. I used doAnswer to capture it.
You need to use doAnswer().when() instead of when().thenAnswer as set method returns void
and finally, atomicInt variable is modified from within the lambda. I made it a field of the class.
As the atomicInt is modified each time, I again used thenAnswer instead of thenReturn for get method.
class BusinessServiceTest {
#Mock
private RedisService redisService;
#InjectMocks
BusinessService businessService = new BusinessService();
AtomicInteger atomicInt = null;
#BeforeEach
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void myTest() throws Exception {
// given
Mockito.when(redisService.isExist("MyKey"))
.thenReturn(false)
.thenReturn(true);
Mockito.doAnswer((Answer<Void>) invocation -> {
atomicInt = invocation.getArgument(1);
return null;
}).when(redisService).set(eq("MyKey"), any(AtomicInteger.class), eq(false));
Mockito.when(redisService.get("MyKey", AtomicInteger.class))
.thenAnswer(invocation -> atomicInt);
// when
for (int i = 0; i < 50; i++) {
Whitebox.invokeMethod(businessService, "count");
}
// Remaining code
}
}
Having said that, I still find your code questionable.
You store AtomicInteger in Redis cache (by serializing it to String). This class is designed to be used by multiple threads in a process, and the threads using it the same counter need to share the same instance. By serializing it and deserializing on get, you are getting multiple instances of the (conceptually) same counter, which, to my eyes, looks like a bug.
smaller issue: You shouldn't normally test private methods
2 small ones: there is no need to instantiate the field annotated with #InjectMocks. You don't need #Spy as well.
I got some questions regarding the use of threads, specially when you have to wait for a thread to be finished so you can perform other operations.
In my app, I use threads for operations such as http connections or when I read from or write to a RecordStore.
For example in the following class that I use to initialize my thread, I retrieve some customers from a webservice using the method called HttpQueryCustomers.
public class thrLoadCustomers implements Runnable {
private RMSCustomer mRMSCustomer;
private String mUrl;
public thrLoadCustomers(RMSCustomer rmsCust, String url) {
mRMSCustomer = rmsCust;
mUrl = url;
}
public void run() {
String jsonResultados = "";
try {
jsonResultados = HttpQueryCustomers();
} catch (IOException ex) {
//How to show a message from here??
} catch (SecurityException se) {
//How to show a message here??
} catch (NullPointerException npe) {
//How to show a message from here??
}
if (!jsonResultados.equals("")) {
try {
mRMSCustomer.save(jsonResultados);
} catch (RecordStoreException ex) {
//How to show a message from here???
}
}
}
public String HttpQueryCustomers() throws IOException,SecurityException,NullPointerException {
StringBuffer stringBuffer = new StringBuffer();
HttpConnection hc = null;
InputStream is = null;
System.out.println(mUrl);
try {
hc = (HttpConnection) Connector.open(mUrl);
if (hc.getResponseCode() == HttpConnection.HTTP_OK) {
is = hc.openInputStream();
int ch;
while ((ch = is.read()) != -1) {
stringBuffer.append((char) ch);
}
}
} finally {
is.close();
hc.close();
}
String jsonData = stringBuffer.toString();
return jsonData.toString();
}
}
Notice in the above class that I pass a parameter called rmsCust of the type RMSCustomer
RMSCustomer is a class that I use to handle all the operations related to RMS:
public class RMSCustomer {
private String mRecordStoreName;
private Customer[] mCustomerList;
public RMSCustomer(String recordStoreName) {
mRecordStoreName = recordStoreName;
}
public Customer[] getCustomers() {
return mCustomerList;
}
public Customer get(int index) {
return mCustomerList[index];
}
public void save(String data) throws RecordStoreException,JSONException,NullPointerException {
RecordStore rs = null;
int idNuevoRegistro;
String stringJSON;
try {
rs = RecordStore.openRecordStore(mRecordStoreName, true);
JSONArray js = new JSONArray(data);
//Set the size of the array
mCustomerList = new Customer[js.length()];
for (int i = 0; i < js.length(); i++) {
JSONObject jsObj = js.getJSONObject(i);
stringJSON = jsObj.toString();
idNuevoRegistro = addRecord(stringJSON, rs);
//Add a new Customer to the array
mCustomerList[i] = initializeCustomer(stringJSON, idNuevoRegistro);
}
} finally {
if (rs != null) {
rs.closeRecordStore();
}
}
}
public int addRecord(String stringJSON, RecordStore rs) throws JSONException,RecordStoreException {
byte[] raw = stringJSON.getBytes();
int idNuevoRegistro = rs.addRecord(raw, 0, raw.length);
return idNuevoRegistro;
}
public Customer initializeCustomer(String stringJSON, int idRecord) throws JSONException {
Customer c = new Customer();
JSONObject jsonObj = new JSONObject(stringJSON);
// Set Customer properties
//...
return c;
}
}
This class is used to show a list of customer and ,as you can see, it extends the List class and receives an array of Customers as a parameter.
public class ListCustomers extends List {
private final Customer[] mData;
public static ListCustomers create(Customer[] data) {
int i = 0;
for (; i < data.length; i++) {
if (data[i] == null) {
break;
}
}
String[] names = new String[i];
for (int j = 0; j < i; j++) {
names[j] = data[j].name;
}
return new ListCustomers(names, data);
}
protected ListCustomers(String names[], Customer[] data) {
super("List of Customer", IMPLICIT, names, null);
mData = data;
}
public Customer getSelectedObject() {
return mData[this.getSelectedIndex()];
}
}
Finally this is how I call the thread from the MIDlet (using all the 3 previous classes) when I want to show a List of Customers:
private void showCustomerList(String url) {
showWaitForm();
if (scrCustomerList == null) {
rmsCustomers = new RMSCustomer("rmsCustomers");
thrLoadCustomers load = new thrLoadCustomers(rmsCustomers, url);
Thread t = new Thread(load);
t.start();
try {
t.join();
} catch (InterruptedException ex) {
}
scrCustomerList = ListCustomers.create(rmsCustomers.getCustomers());
scrCustomerList.addCommand(cmdSelect);
scrCustomerList.addCommand(cmdBack);
scrCustomerList.setCommandListener(this);
}
mDisplay.setCurrent(scrCustomerList);
}
Now here's the problems I have :
The showWaitForm() doesn't work (it sets a form with a Gauge as the
Current form)
I don't know how to show all the exceptions that might be thrown from
within the thrLoadCustomers class.
I don't know whether using t.join() is the best choice
The last question is about something the book I'm reading says :
Threads, in particular, can be a scarce commodity. The MSA
specification requires that an application must be allowed to create
ten threads. Just because you can doesn’t mean you should. In general,
try to use the fewest resources possible so that your application will
run as smoothly as possible
This is the first time a use threads, and in my app I might have up to 10 threads (classes). However, I will only execute once thread at the time, will I be going against what the previous quotation says??
I hope I'm not asking too many questions. Thank you very much for your help.
P.D Much of the code I posted here wouldn't have been possible with the help of Gregor Ophey
Question #1 is about a different problem not related to threading, and for which very little code is shown. I'd suggest you to post a new dedicated question with proper explanation of the issue.
Questions #2 and #3: You could define a wrapper class like this:
public class WSResult {
private boolean success; //true if the WS call went ok, false otherwise
private String errorMessage; //Error message to display if the WS call failed.
private Object result; //Result, only if the WS call succeeded.
private boolean completed = false;
//TODO getter and setters methods here
}
In your screen, you can create an instance of result and wait for it:
WSResult result = new WSResult();
//Start thread here
new Thread(new LoadCustomersTask(result)).start();
//This is old school thread sync.
synchronized(result){
while(!result.isCompleted()){
result.wait();
}
}
//Here the thread has returned, and we can diaplay the error message if any
if(result.isSuccess()){
} else {
//Display result.getErrorMessage()
}
Then your runnable would be like this:
class LoadCustomersTask implements Runnable {
private final WSResult result;
public LoadCustomersTask(WSResult res){
result = res;
}
public void run(){
//Do the WS call
//If it went well
result.setSuccess(true);
result.setResult(jsonResultados);
//Else
result.setSuccess(false);
result.setErrorMessage("Your error message");
//In any case, mark as completed
result.setcompleted(true);
//And notify awaiting threads
synchronized(result){
result.notifyAll();
}
}
}
You can also do it with thread.join, but wait/notify is better because you not making the screen depend on the particular thread where the runnable runs. You can wait/notify on the result instance, as shown, or on the runnable if it is intended for a single use.
Question #4: Yes threads must not be abused, specially in JavaME where programs usually run in single core CPUs with a frecuency in the order of MHz. Try not to have more than 1-3 threads running at the same time. If you really need to, consider using a single thread for running all background tasks (a blocking queue).
Say I have a bunch of transformations on an Observable:
operation()
.flatMap(toSomething())
.map(toSomethingElse())
.flatMap(toYetSomethingElse())
.subscribeOn(Schedulers.newThread())
.observeOn(AdroidSchedulers.mainThread())
.subscribe(observer);
Are all of these operations synchronous except for the last call to flatMap()? Or are all of the operations run on the thread that I told it to subscribe on?
I figured this out, with a test. The following test passes (which means the emissions on the Observable are all on the same background thread):
volatile long observableThreadId;
#Test
public void transformedObservables_shouldRunInSameThread() {
Observable.from(new String[]{"a", "b", "c"}) //
.flatMap(new Func1<String, Observable<Object>>() {
#Override public Observable<Object> call(String s) {
observableThreadId = Thread.currentThread().getId();
return Observable.from((Object) s);
}
}) //
.map(new Func1<Object, String>() {
#Override public String call(Object o) {
long id = Thread.currentThread().getId();
if (id != observableThreadId) {
throw new RuntimeException("Thread ID mismatch");
}
return (String) o;
}
}) //
.flatMap(new Func1<String, Observable<String>>() {
#Override public Observable<String> call(String s) {
long id = Thread.currentThread().getId();
if (id != observableThreadId) {
throw new RuntimeException("Thread ID mismatch");
}
return Observable.from(s);
}
}) //
.subscribeOn(Schedulers.newThread()) //
.observeOn(Schedulers.currentThread()) //
.subscribe(new Observer<String>() {
#Override public void onCompleted() {
assertThat(Thread.currentThread().getId()).isNotEqualTo(observableThreadId);
}
#Override public void onError(Throwable throwable) {
}
#Override public void onNext(String s) {
}
});
System.out.println("blah");
}
===============================
UPDATE:
A better answer can actually be found in the ReactiveX documentation on Scheduler:
By default, an Observable and the chain of operators that you apply to
it will do its work, and will notify its observers, on the same thread
on which its Subscribe method is called. The SubscribeOn operator
changes this behavior by specifying a different Scheduler on which the
Observable should operate. The ObserveOn operator specifies a
different Scheduler that the Observable will use to send notifications
to its observers.
... the SubscribeOn operator designates which thread the Observable will
begin operating on, no matter at what point in the chain of operators
that operator is called. ObserveOn, on the other hand, affects the
thread that the Observable will use below where that operator appears.
For this reason, you may call ObserveOn multiple times at various
points during the chain of Observable operators in order to change on
which threads certain of those operators operate.
I'm trying to get windows workflows working, and I've become a little stumped.
I've gotten a single workflow working, but now I am trying to do something a little more complex: start a workflow, where each activity itself contains a workflow. (Picture something like the main program starts the activities "Input, logic, and output", and then each of those have additional activities like "prompt user, get input, etc.")
I've had it working fine, with the example from here (http://msdn.microsoft.com/en-us/magazine/gg535667.aspx), when I am not passing any parameters from the main program to the activites. My question is, how exactly does the 'Variables' and 'metadata.SetVariablesCollection' work in the NativeActivity, and how to I get the parameters to the low level activities?
This is what I am currently trying:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using System.Collections.ObjectModel;
using System.Activities.Statements;
namespace Project1
{
internal class MainProgram
{
internal static void Main(string[] args)
{
try
{
var act = new SimpleSequence();
act.Activities.Add((Activity)(new WriteSomeText()));
act.Activities.Add((Activity)(new WriteSomeText()));
act.Activities.Add((Activity)(new WriteSomeText()));
act.Variables.Add(new Variable<string> ("stringArg", "TEXT"));
WorkflowInvoker.Invoke(act);
}
catch (Exception ex)
{
System.Console.WriteLine("EXCEPTION: {0}", ex);
}
}
public class WriteSomeText : CodeActivity
{
[RequiredArgument]
public InArgument<string> stringArg { get; set; }
protected override void Execute(CodeActivityContext context)
{
string output = context.GetValue(stringArg);
System.Console.WriteLine(output);
}
}
public class SimpleSequence : NativeActivity
{
Collection<Activity> activities;
Collection<Variable> variables;
Variable<int> current = new Variable<int> { Default = 0 };
public Collection<Activity> Activities
{
get
{
if (this.activities == null)
this.activities = new Collection<Activity>();
return this.activities;
}
set
{
this.activities = value;
}
}
public Collection<Variable> Variables
{
get
{
if (this.variables == null)
this.variables = new Collection<Variable>();
return this.variables;
}
set
{
this.variables = value;
}
}
protected override void CacheMetadata(NativeActivityMetadata metadata)
{
metadata.SetChildrenCollection(this.activities);
metadata.SetVariablesCollection(this.variables);
metadata.AddImplementationVariable(this.current);
}
protected override void Execute(NativeActivityContext context)
{
if (this.Activities.Count > 0)
context.ScheduleActivity(this.Activities[0], onChildComplete);
}
void onChildComplete(NativeActivityContext context, ActivityInstance completed)
{
int currentExecutingActivity = this.current.Get(context);
int next = currentExecutingActivity + 1;
if (next < this.Activities.Count)
{
context.ScheduleActivity(this.Activities[next], this.onChildComplete);
this.current.Set(context, next);
}
}
}
}
}
This ends up throwing the following exception:
EXCEPTION: System.Activities.InvalidWorkflowException: The following errors were encountered while processing the workflow tree:
'WriteSomeText': Value for a required activity argument 'stringArg' was not supplied.
'WriteSomeText': Value for a required activity argument 'stringArg' was not supplied.
'WriteSomeText': Value for a required activity argument 'stringArg' was not supplied.
at System.Activities.Validation.ActivityValidationServices.ThrowIfViolationsExist(IList`1 validationErrors)
at System.Activities.Hosting.WorkflowInstance.ValidateWorkflow(WorkflowInstanceExtensionManager extensionManager)
at System.Activities.Hosting.WorkflowInstance.RegisterExtensionManager(WorkflowInstanceExtensionManager extensionManager)
at System.Activities.WorkflowApplication.EnsureInitialized()
at System.Activities.WorkflowApplication.RunInstance(WorkflowApplication instance)
at System.Activities.WorkflowApplication.Invoke(Activity activity, IDictionary`2 inputs, WorkflowInstanceExtensionManager extensions, TimeSpan timeout)
at System.Activities.WorkflowInvoker.Invoke(Activity workflow, TimeSpan timeout, WorkflowInstanceExtensionManager extensions)
at System.Activities.WorkflowInvoker.Invoke(Activity workflow)
at Project1.MainProgram.Main(String[] args) in c:\users\user\documents\visual studio 2010\Projects\ModelingProject1\Project1\MainProgram.cs:line 25
I know, I only pass 1 parameter, but the exception still says that I am missing 3 parameters. I am missing something as to how to do this properly.
You're correctly declaring stringArg as an InArgument but you're not passing any value to it when calling it inside SimpleSequence.
You can pass something using the constructor, while constructing the all activity itself, like this:
public class WriteSomeText : CodeActivity
{
[RequiredArgument]
public InArgument<string> stringArg { get; set; }
public WriteSomeText(string stringArg)
{
this.stringArg = stringArg;
}
protected override void Execute(CodeActivityContext context
{
string output = context.GetValue(stringArg);
System.Console.WriteLine(output);
}
}
// Calling the activity like this:
internal static void Main(string[] args)
{
var act = new SimpleSequence()
{
Activities =
{
new WriteSomeText("hello"),
new WriteSomeText("world"),
new WriteSomeText("!")
}
};
WorkflowInvoker.Invoke(act);
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
Also notice that is a best practice to use the constructor to initialize collections:
public SimpleSequence()
{
activities = new Collection<Activity>();
variables = new Collection<Variable>();
}
This way is even more intuitive to initialize the activity:
var act = new SimpleSequence()
{
Activities =
{
new WriteSomeText("hello"),
new WriteSomeText("world"),
new WriteSomeText("!")
},
Variables =
{
new Variable<int>("myNewIntVar", 10),
// ....
}
};
EDIT:
There are a couple of other ways to approach the problem. This is your best friend while starting in the WF4 world.
Check WF\Basic\CustomActivities\Code-Bodied for a little push with this particular case.
After we have done some performance testing for our application which uses jackrabbit we faced with the huge problem with concurrent modification jackrabbit's repository. Problem appears when we add nodes or edit them in multithread emulation. Then I wrote very simple test which shows us that problem is not in our environment.
There is it:
Simple Stateless Bean
#Stateless
#Local(TestFacadeLocal.class)
#Remote(TestFacadeRemote.class)
public class TestFacadeBean implements TestFacadeRemote, TestFacadeLocal {
public void doAction(int name) throws Exception {
new TestSynch().doAction(name);
}
}
Simple class
public class TestSynch {
public void doAction(int name) throws Exception {
Session session = ((Repository) new InitialContext().
lookup("java:jcr/local")).login(
new SimpleCredentials("username", "pwd".toCharArray()));
List added = new ArrayList();
Node folder = session.getRootNode().getNode("test");
for (int i = 0; i <= 100; i++) {
Node child = folder.addNode("" + System.currentTimeMillis(),
"nt:folder");
child.addMixin("mix:versionable");
added.add(child);
}
// saving butch changes
session.save();
//checking in all created nodes
for (Node node : added) {
session.getWorkspace().getVersionManager().checkin(node.getPath());
}
}
}
And Test class
public class Test {
private int c = 0;
private int countAll = 50;
private ExecutorService executor = Executors.newFixedThreadPool(5);
public ExecutorService getExecutor() {
return executor;
}
public static void main(String[] args) {
Test test = new Test();
try {
test.start();
} catch (Exception e) {
e.printStackTrace();
}
}
private void start() throws Exception {
long time = System.currentTimeMillis();
TestFacadeRemote testBean = (TestFacadeRemote) getContext().
lookup( "test/TestFacadeBean/remote");
for (int i = 0; i < countAll; i++) {
getExecutor().execute(new TestInstallerThread(i, testBean));
}
getExecutor().shutdown();
while (!getExecutor().isTerminated()) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(c + " shutdown " +
(System.currentTimeMillis() - time));
}
class TestInstallerThread implements Runnable {
private int number = 0;
TestFacadeRemote testBean;
public TestInstallerThread(int number, TestFacadeRemote testBean) {
this.number = number;
this.testBean = testBean;
}
#Override
public void run() {
try {
System.out.println("Installing data " + number);
testBean.doAction(number);
System.out.println("STOP" + number);
} catch (Exception e) {
e.printStackTrace();
c++;
}
}
}
public Context getContext() throws NamingException {
Properties properties = new Properties();
//init props
..............
return new InitialContext(properties);
}
}
If I initialized executor with 1 thread in pool all done without any error. If I initialized executor with 5 thread I got sometimes errors:
on client
java.lang.RuntimeException: javax.transaction.RollbackException: [com.arjuna.ats.internal.jta.transaction.arjunacore.commitwhenaborted] [com.arjuna.ats.internal.jta.transaction.arjunacore.commitwhenaborted] Can't commit because the transaction is in aborted state
at org.jboss.aspects.tx.TxPolicy.handleEndTransactionException(TxPolicy.java:198)
on server at the beginning warn
ItemStateReferenceCache [ItemStateReferenceCache.java:176] overwriting cached entry 187554a7-4c41-404b-b6ee-3ce2a9796a70
and then
javax.jcr.RepositoryException: org.apache.jackrabbit.core.state.ItemStateException: there's already a property state instance with id 52fb4b2c-3ef4-4fc5-9b79-f20a6b2e9ea3/{http://www.jcp.org/jcr/1.0}created
at org.apache.jackrabbit.core.PropertyImpl.restoreTransient(PropertyImpl.java:195) ~[jackrabbit-core-2.2.7.jar:2.2.7]
at org.apache.jackrabbit.core.ItemSaveOperation.restoreTransientItems(ItemSaveOperation.java:879) [jackrabbit-core-2.2.7.jar:2.2.7]
We have tried synchronize this method and other workflow for handle multithread calls as one thread. Nothing helps.
And one more thing - when we have done similar test without ejb layer - all worked fine.
It looks like container wrapped in own transaction and then all crashed.
Maybe somebody faced with such a problem.
Thanks in advance.
From the Jackrabbit Wiki:
The JCR specification explicitly states that a Session is not thread-safe (JCR 1.0 section 7.5 and JCR 2.0 section 4.1.2). Hence, Jackrabbit does not support multiple threads concurrently reading from or writing to the same session. Each session should only ever be accessed from one thread.
...
If you need to write to the same node concurrently, then you need to use multiple sessions, and use JCR locking to ensure there is no conflict.