synchronized block on grails works on windows but

2019-02-21 04:03发布

问题:

I have a grails application that relies on a synchronized block into a service. When I run it on windows the synchronization works as expected but when I run on ams linux a get a StaleObjectStateException.

The problem is reproduced in the following example.

class TestService {

private final Object $lock = new Object[0];

TesteSync incrementa() {

    synchronized ($lock) {
        TesteSync t = TesteSync.findById(1)
        t.contador++
        t.save(flush: true)

        Thread.sleep(10000)

        return t
    }
}

}

In my understand, this exception occurs because multiple threads are trying to save the same object. That's why I'm using a synchronized block.

Linux java:

  • java version "1.7.0_85"
  • OpenJDK Runtime Environment (amzn-2.6.1.3.61.amzn1-x86_64 u85-b01)
  • OpenJDK 64-Bit Server VM (build 24.85-b03, mixed mode)

Windows java:

  • java version "1.7.0_79"
  • Java(TM) SE Runtime Environment (build 1.7.0_79-b15)
  • Java HotSpot(TM) 64-Bit Server VM (build 24.79-b02, mixed mode)

Any clues?

Thanks

回答1:

You are right about why you're getting the StaleObjectStateException.

If what you're looking for is pessimistic locking (allowing only one transaction access to the data at any given time), then you can use the domain class lock() method:

class TestService {
    static transactional = true

    TesteSync incrementa() {
        TesteSync t = TesteSync.lock(1)
        t.contador++
        return t.save()
    }
}

You can learn more about Grails pessimistic locking here.

PS: Grails services are transactional by default. But in my example I explicitly made the service transactional to call something to your attention: The lock is released by Grails automatically when the transaction commits. I also removed the flush because the data gets flushed when the transaction commits. If you were doing this from a controller method that's not explicitly set to @Transactional, then you would need the flush.

TIP: When you query by ID you can do this...

SomeDomainClass.get(1)

...instead of this...

SomeDomainClass.findById(1)

Espero que ajude.