【问题标题】:Java synchronized method is not synchronizedJava同步方法不同步
【发布时间】:2016-05-06 10:32:10
【问题描述】:

我有 JAX-RS、Guice、MyBatis 的项目。有通过 REST 端点调用的方法 getToken()。由于@Transactional(isolation = Isolation.SERIALIZABLE),它被同步以避免异常。但是同步方法并不安全,不同的调用可能会同时影响数据并抛出异常:

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

我尝试通过映射器对象进行同步,但它也不起作用。唯一有效的解决方案是删除同步并更改/删除隔离级别。如何使方法同步?

@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
    }
}

【问题讨论】:

  • 除了getToken()还有什么方法可以调用数据库?
  • 还有其他获取和删除令牌的方法。

标签: java multithreading concurrency synchronized transactional


【解决方案1】:

这不是关于synchronized 不能正常工作,而是关于@Transactional 是如何实现的。

长话短说:Spring 没有直接调用事务方法(在您的情况下为 getToken()),而是创建了代理类,它用类似这样的方式(非常简化)替换了所有事务方法:

// 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();
    }
}

更多详情请见this answer

如您所见,首先打开事务,然后才调用您原来的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();
    }
}

【讨论】:

  • 尝试在 makeSomeAction() 中与 synchronized(this) 同步,同步方法但得到相同:原因:org.postgresql.util.PSQLException:错误:由于读/写无法序列化访问交易之间的依赖关系。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-10
  • 2014-07-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-30
相关资源
最近更新 更多