Some problems and solutions when @Transactional and synchronized are used at the same time

Using @Transactional and synchronized at the same time does not guarantee transactional consistency

background

Everything has a background.
There is a requirement [a business contains multiple transactions, and it also needs to avoid the influence of other threads. Fortunately, the service only needs to start a single instance, otherwise the impact of distribution must be considered]
My The idea is to use @Transactional and synchronized to ensure transaction consistency and multi-threading impact, but it turns out that it doesn't do what it wants

Analyze the reasons

    @Transactional
    public ResultVo service(){
        synchronized (LOCK){
            //doservice
        }
    }
About why the code block lock is used instead of the synchronized keyword to not affect other methods. The keyword locks the current class object by default.
At my code is like this. At first glance, there seems to be no problem, but why does it go wrong? Woolen cloth

Troubleshooting

Problem reproduction: The problem must be reproduced. Any problem that cannot be reproduced is not a problem. Any problem that exists must be reproduced.
From near to far: first confirm that your own code is ok, and then consider external code (such as the second party library, tripartite library)
from inside to outside: The essence of the program is IPO, including input (input), program (program)/instruction set, output (output), first confirm that there is no problem with the input, and then confirm the code logic. From
shallow to deep Difficulty, from top to bottom, first the upper layer API, http transmission, etc., and then the bottom layer API, source code, jvm, etc.

Having said that, the problem is easier to analyze

First my input is fine

Secondly, there is no problem with my logic code

Next is the two-party library and the three-party library.

Since the transaction uses the spring transaction, it is implemented based on aop, ok found the problem

Because spring's aop will open the transaction before the method modified by @Transactional, and then add the lock. When the locked code is executed, the transaction is committed. Therefore, the execution of the
synchronized code block is executed within the transaction. It can be inferred that in When the code block is executed, the transaction has not been committed, and after other threads enter the synchronized code block, the inventory data read is not the latest.

Solve the problem

I saw a lot of solutions on the Internet saying to set a method on the outer layer to increase the lock level, or to add a lock to the controller
, which rolled back
. In spring transaction management, use Synchronized to modify the transaction method and synchronize Why does it fail
https://blog.csdn.net/weixin_54401017/article/details/129768305
Here is a solution, using the thread pool, of course the business code still needs to add @Transactional!!!
private ExecutorService executorService = null;//线程池

public ResultVo updateOk(@RequestBody OtcTransferOutManageVo otcTransferOutManageVo){
        //如果线程池为null或则线程池被关闭了,创建一个单线程化线程池
        if (executorService == null || executorService.isShutdown()) {
            executorService = Executors.newSingleThreadExecutor();
        }
        if (otcTransferOutManageVo.getId() == null) {
            return ResultVoBuildUtils.buildResultVo( Constants.FAIL, "参数错误" );
        }
        //使用submit执行业务  Future和result.get()是为了保障线程同步,不然变成异步线程是无法捕获异常信息的
        Future<ResultVo> result = executorService.submit(()->{
            ResultVo resultVo = otcTransferOutManageService.updateOkStatus(otcTransferOutManageVo);
            LoggerHepler.writeInfoLog( TransferInController.class, resultVo.getMsg() );
            return resultVo;
        });
         executorService.shutdown();
        try {
            return result.get();
        } catch (Exception e) {
            LoggerHepler.writeErrorLog( TransferOutManageController.class, ServiceTypeENUM.ASSET_MANAGEMEN, BusinessTypeENUM.TRADE,
                   ExceptionCodeConstants.UPDATE_MYSQL_EXCEPTION, "updateOk error!", e );
            return ResultVoBuildUtils.buildFaildResultVo();
        }
    }
A few things to say about this code

newSingleThreadExecutor() creates a single-threaded thread pool

You can see from the source code:
This method creates a thread pool with a single worker thread. If this thread fails during execution, there will be a new thread to continue to complete the unfinished work. Tasks are guaranteed to be executed sequentially (
string line), and there will be no more than one active thread at any time.
This is based on LinkedBlockingQueue, which is a thread-safe blocking queue

shutdown() method

在调用这个方法后,会在submit的任务执行完成后将线程池变为shutdown状态,拒绝新的任务(线程池不会立刻退出,直到任务完成)
如果已经关闭了,调用此方法也不会有额外的影响
此时不能再往线程池中添加新任务,否则会抛出RejectedExecutionException异常。
  • Future && get()

因为这里需要获取线程执行的返回值,
无论是继承Thread类还是实现Runnable接口都无法获取到线程执行的返回值(默认是异步线程)
所以这里用到的是线程的第三种创建方式,实现callable接口重写call方法,当然重写call方法被我用lambda表达式隐含了
所以get()就是为了获取线程执行的返回值

submit()方法

传入一个Callable 任务,返回执行完成的返回值

Guess you like

Origin blog.csdn.net/weixin_54401017/article/details/129768215