简体   繁体   中英

Java synchronized method is not synchronized

I have project with JAX-RS, Guice, MyBatis. There is method getToken() which is invoked through REST endpoint. It is synchronized to avoid exceptions because of @Transactional(isolation = Isolation.SERIALIZABLE) . However, synchronized method is not safe, different calls may affect data concurrently and exception is thrown:

Cause: org.postgresql.util.PSQLException: ERROR: could not serialize access due to read/write dependencies among transactions

I have tried to synchronize by mapper object but it also does not work. The only solution which worked was to remove synchronized and change/remove isolation level. How to make method synchronized?

@Singleton
@Path("/forgottenPath")
public class RestEndpoint {

    @Inject
    private oneService oneService;

    @POST
    @Path("/someAction")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public SomeResponse makeSomeAction() {
        ...
        oneService.makeSomeAction();
        ...
    }
}

public class OneServiceImpl implements OneService {

    @Inject
    private AnotherService anotherService;

    @Override
    public SomeRespose makeSomeAction() {
        ...
        anotherService.getToken());
        ....
    }
}

@Singleton
public class AnotherServiceImpl implements AnotherService {

    @Override
    @Transactional(isolation = Isolation.SERIALIZABLE)
    public synchronized Token getToken() {
        // modifies and retrieves info from database
    }
}

It's not about synchronized doesn't work properly, it's about how @Transactional is implemented.

Long story short: instead of directly calling transactional method ( getToken() in your case), Spring creates proxy class which replaces all transactional methods with something like this (very simplified):

// Generated proxy class (either via AOP, dynamic proxy or bytecode generation)
@Override
public Token getToken() {
    try {
        transactionManager.startTransaction(params);
        // Only this call is synchronized
        return super.getToken();
    }
    catch (Throwable e) {
        transactionManager.rollback();
        rethrow();
    }
    finally {
        // Not in synchronized method (lock is not held), but changes are not commited yet
        transactionManager.commit();
        transactionManager.closeTransaction();
    }
}

See this answer for more details.

As you can see, firstly transaction is opened and only then your original getToken() is called, so in fact, when you try to acquire lock (enter synchronized method), transaction is already created. Moreover, when caller exit your getToken() method lock is released (exit from synchronized method), but transaction is not yet commited . So possible race is here:

Assume first thread opens transaction, hold lock, do stuff with database, exit your original method, release lock and then paused a bit. Then second thread can do the same actions, first thread is awaken and they both try to commit, so one of transactions should fail.

Answering your original question, to avoid changing isolation level and allow serialized access you need to synchronize not in your service, but around it.

Three solutions:

1) Make caller method synchronozed ( makeSomeAction in your case)

2) If you don't want whole method be synchronized, create lock for it:

@Override
public SomeRespose makeSomeAction() {
    ...
    // Instance of ReentrantLock
    lock.lock();
    try {
        anotherService.getToken());
    }
    finally {
        lock.unlock();
    }
    ....
}

3) If you want to encapsulate synchronization logic, create blocking adapter:

@Singleton
public class AnotherServiceAdapter implements AnotherService {

    @Autowired
    private AnotherServiceImpl originalService;

    @Override // No transactional here => no aop proxy
    public synchronized Token getToken() {
        // Lock is held before transactional code kicks in
        return originalService.getToken();
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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