Java 同步方法不同步
Java synchronized method is not synchronized
我有 JAX-RS、Guice、MyBatis 的项目。有通过 REST 端点调用的方法 getToken()
。它是同步的,以避免因为 @Transactional(isolation = Isolation.SERIALIZABLE)
而出现异常。但是synchronized方法并不安全,不同的调用可能会同时影响数据并抛出异常:
Cause: org.postgresql.util.PSQLException: ERROR: could not serialize access due to read/write dependencies among transactions
我已经尝试通过映射器对象进行同步,但它也不起作用。唯一有效的解决方案是删除同步和 change/remove 隔离级别。如何使方法同步?
@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
}
}
不是synchronized
不能正常工作,而是@Transactional
是如何实现的。
长话短说:Spring 不是直接调用事务方法(在你的情况下是 getToken()
),而是创建代理 class,用这样的东西替换所有事务方法(非常简化):
// 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();
}
}
有关详细信息,请参阅 。
如你所见,首先打开事务,然后才调用你原来的getToken()
,所以实际上,当你尝试获取锁(进入同步方法)时,事务是已经 创建。此外,当调用者退出时,您的 getToken()
方法锁被释放(从同步方法退出),但事务 尚未提交 。所以可能的比赛在这里:
假设第一个线程打开事务,持有锁,处理数据库,退出原来的方法,释放锁然后暂停一下。然后第二个线程可以执行相同的操作,第一个线程被唤醒并且它们都尝试提交,因此其中一个事务 应该 失败。
回答你最初的问题,为了避免改变隔离级别并允许序列化访问,你需要同步的不是你的服务,而是围绕它。
三种解决方案:
1) 使调用方方法同步(makeSomeAction
在你的情况下)
2) 如果你不想让整个方法同步,为它创建锁:
@Override
public SomeRespose makeSomeAction() {
...
// Instance of ReentrantLock
lock.lock();
try {
anotherService.getToken());
}
finally {
lock.unlock();
}
....
}
3)如果要封装同步逻辑,创建阻塞适配器:
@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();
}
}
我有 JAX-RS、Guice、MyBatis 的项目。有通过 REST 端点调用的方法 getToken()
。它是同步的,以避免因为 @Transactional(isolation = Isolation.SERIALIZABLE)
而出现异常。但是synchronized方法并不安全,不同的调用可能会同时影响数据并抛出异常:
Cause: org.postgresql.util.PSQLException: ERROR: could not serialize access due to read/write dependencies among transactions
我已经尝试通过映射器对象进行同步,但它也不起作用。唯一有效的解决方案是删除同步和 change/remove 隔离级别。如何使方法同步?
@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
}
}
不是synchronized
不能正常工作,而是@Transactional
是如何实现的。
长话短说:Spring 不是直接调用事务方法(在你的情况下是 getToken()
),而是创建代理 class,用这样的东西替换所有事务方法(非常简化):
// 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();
}
}
有关详细信息,请参阅
如你所见,首先打开事务,然后才调用你原来的getToken()
,所以实际上,当你尝试获取锁(进入同步方法)时,事务是已经 创建。此外,当调用者退出时,您的 getToken()
方法锁被释放(从同步方法退出),但事务 尚未提交 。所以可能的比赛在这里:
假设第一个线程打开事务,持有锁,处理数据库,退出原来的方法,释放锁然后暂停一下。然后第二个线程可以执行相同的操作,第一个线程被唤醒并且它们都尝试提交,因此其中一个事务 应该 失败。
回答你最初的问题,为了避免改变隔离级别并允许序列化访问,你需要同步的不是你的服务,而是围绕它。
三种解决方案:
1) 使调用方方法同步(makeSomeAction
在你的情况下)
2) 如果你不想让整个方法同步,为它创建锁:
@Override
public SomeRespose makeSomeAction() {
...
// Instance of ReentrantLock
lock.lock();
try {
anotherService.getToken());
}
finally {
lock.unlock();
}
....
}
3)如果要封装同步逻辑,创建阻塞适配器:
@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();
}
}