Why Combining synchronized with @Transactional Causes Thread‑Safety Issues in Spring and How to Resolve Them

The article explains why using the synchronized keyword together with Spring's @Transactional annotation can lead to lost updates when many threads increment a database field, analyzes the underlying transaction‑proxy interaction, and presents a refactored solution that moves the lock to a separate service to ensure correct results.

Java Captain
Java Captain
Java Captain
Why Combining synchronized with @Transactional Causes Thread‑Safety Issues in Spring and How to Resolve Them

In response to a Zhihu question about a synchronized method that increments a database column but yields a final value lower than the expected total, the author investigates the root cause of the thread‑safety problem.

Initial reasoning assumes that the synchronized method guarantees ordering, atomicity, and visibility, and that the @Transactional annotation should ensure each increment is committed, so the final count should equal the number of threads.

Through testing with 1,000 concurrent threads, the author discovers that the addEmployee() method is not executed serially; multiple threads interleave their database reads and writes, causing lost updates.

The deeper analysis reveals that Spring’s transaction management is implemented via dynamic proxies (AOP). When a method annotated with @Transactional is also synchronized, the lock only protects the Java method body, while the transaction commit occurs after the lock is released. Consequently, another thread can enter the synchronized method before the previous transaction is committed, reading stale data and causing inconsistent updates.

To fix the issue, the author proposes moving the synchronized block to a separate service that wraps the transactional call, ensuring the lock covers the entire transaction lifecycle. The revised code is shown below:

@RestController
public class EmployeeController {
    @Autowired
    private SynchronizedService synchronizedService;

    @RequestMapping("/add")
    public void addEmployee() {
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> synchronizedService.synchronizedAddEmployee()).start();
        }
    }
}

// New service that holds the lock
@Service
public class SynchronizedService {
    @Autowired
    private EmployeeService employeeService;

    public synchronized void synchronizedAddEmployee() {
        employeeService.addEmployee();
    }
}

@Service
public class EmployeeService {
    @Autowired
    private EmployeeRepository employeeRepository;

    @Transactional
    public void addEmployee() {
        Employee employee = employeeRepository.getOne(8);
        System.out.println(Thread.currentThread().getName() + employee);
        Integer age = employee.getAge();
        employee.setAge(age + 1);
        employeeRepository.save(employee);
    }
}

With this arrangement, the synchronized lock now spans the whole transaction, eliminating the lost‑update problem; however, the execution becomes slower due to the serialized access, which is expected.

The article concludes by noting that while Spring transactions are convenient, developers must understand their interaction with Java synchronization to avoid subtle bugs.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Javatransactionconcurrencyspringsynchronizedthread-safety
Java Captain
Written by

Java Captain

Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.