Backend Development 9 min read

Mastering Distributed Locks with Spring Integration in Spring Boot

This guide explains how to use Spring Integration's LockRegistry for distributed locking, covering both database‑based and Redis‑based implementations, complete with Maven dependencies, configuration snippets, bean definitions, and comprehensive test cases to ensure reliable synchronization across multiple nodes.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Mastering Distributed Locks with Spring Integration in Spring Boot

Environment: SpringBoot 2.7.12

This article introduces the distributed lock functionality provided by Spring Integration.

1. Overview

Spring Integration is a framework for building event‑driven applications. Within it, the LockRegistry interface manages distributed locks, which synchronize access to shared resources across multiple nodes.

The LockRegistry and related sub‑interfaces (e.g., RenewableLockRegistry ) provide three main capabilities:

Acquire lock : obtain a lock via LockRegistry when a shared resource is needed.

Release lock : after using the resource, the lock is automatically released internally.

Renew : extend the lock’s hold time when necessary.

Common LockRegistry implementations include database‑based, ZooKeeper, and Redis‑based locks.

2. Database‑Based Distributed Lock

Dependencies

<code>&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
  &lt;artifactId&gt;spring-boot-starter-integration&lt;/artifactId&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.integration&lt;/groupId&gt;
  &lt;artifactId&gt;spring-integration-jdbc&lt;/artifactId&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
  &lt;groupId&gt;com.zaxxer&lt;/groupId&gt;
  &lt;artifactId&gt;HikariCP&lt;/artifactId&gt;
  &lt;scope&gt;compile&lt;/scope&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
  &lt;groupId&gt;mysql&lt;/groupId&gt;
  &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;
  &lt;version&gt;8.0.30&lt;/version&gt;
&lt;/dependency&gt;</code>

Configuration

<code>spring:
  datasource:
    driverClassName: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/spring_lock?serverTimezone=GMT%2B8&amp;nullCatalogMeansCurrent=true&amp;useSSL=false
    username: root
    password: xxxooo
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimumIdle: 10
      maximumPoolSize: 200
---
spring:
  integration:
    jdbc:
      initialize-schema: always
      schema: classpath:schema-mysql.sql</code>

Register core beans

<code>@Bean
public DefaultLockRepository defaultLockRepository(DataSource dataSource) {
    DefaultLockRepository lockRepository = new DefaultLockRepository(dataSource);
    // configure table prefix if needed, default: IN_
    lockRepository.setPrefix("T_");
    return lockRepository;
}

@Bean
public JdbcLockRegistry jdbcLockRegistry(DefaultLockRepository lockRepository) {
    return new JdbcLockRegistry(lockRepository);
}</code>

Test case

<code>@Test
public void testLock() throws Exception {
    int len = 10;
    CountDownLatch cdl = new CountDownLatch(len);
    CountDownLatch waiter = new CountDownLatch(len);
    Thread[] ts = new Thread[len];
    for (int i = 0; i < len; i++) {
        ts[i] = new Thread(() -> {
            waiter.countDown();
            System.out.println(Thread.currentThread().getName() + " - 准备获取锁");
            try { waiter.await(); } catch (InterruptedException e1) { e1.printStackTrace(); }
            // acquire lock
            Lock lock = registry.obtain("drug_store_key_001");
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " - 获取锁成功");
            try {
                try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
            } finally {
                // release lock
                lock.unlock();
                cdl.countDown();
                System.out.println(Thread.currentThread().getName() + " - 锁释放成功");
            }
        }, "T - " + i);
    }
    for (int i = 0; i < len; i++) { ts[i].start(); }
    cdl.await();
}</code>

Database diagram

The JdbcLock implementation of java.util.concurrent.locks.Lock supports re‑entrancy. The retry interval after a lock acquisition failure defaults to 100 ms.

<code>JdbcLockRegistry jdbcLockRegistry = new JdbcLockRegistry(lockRepository);
// set retry interval when lock acquisition fails
jdbcLockRegistry.setIdleBetweenTries(Duration.ofMillis(200));</code>

Lock renewal

<code>jdbcLockRegistry.renewLock("drug_store_key_001");</code>

3. Redis‑Based Distributed Lock

Dependencies

<code>&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.integration&lt;/groupId&gt;
  &lt;artifactId&gt;spring-integration-jdbc&lt;/artifactId&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
  &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;
  &lt;artifactId&gt;spring-boot-starter-data-redis&lt;/artifactId&gt;
&lt;/dependency&gt;</code>

Configuration

<code>spring:
  redis:
    host: localhost
    port: 6379
    password: xxxooo
    database: 8
    lettuce:
      pool:
        maxActive: 8
        maxIdle: 100
        minIdle: 10
        maxWait: -1</code>

Test case (same as JDBC, only lock acquisition line changes)

<code>Lock lock = redisLockRegistry.obtain("001");</code>

Set lock expiration (default 60 s)

<code>// third argument sets key expiration, here changed to 10 s
RedisLockRegistry redisLockRegistry = new RedisLockRegistry(connectionFactory, registryKey, 10000);
</code>

Note: If the Redis key expires after 10 s, the lock will be released and further operations may fail because there is no Redisson watchdog mechanism.

<code>Exception in thread "T - 0" java.lang.IllegalStateException: Lock was released in the store due to expiration. The integrity of data protected by this lock may have been compromised.
  at org.springframework.integration.redis.util.RedisLockRegistry$RedisLock.unlock(RedisLockRegistry.java:450)
  at com.pack.SpringIntegrationDemoApplicationTests.lambda$1(SpringIntegrationDemoApplicationTests.java:83)
  at java.lang.Thread.run(Thread.java:748)</code>

Even after the key is deleted, other threads cannot immediately acquire the lock because a local ReentrantLock must be obtained first.

<code>private abstract class RedisLock implements Lock {
    private final ReentrantLock localLock = new ReentrantLock();
    public final void lock() {
        this.localLock.lock();
        while (true) {
            try {
                if (tryRedisLock(-1L)) {
                    return;
                }
            } catch (InterruptedException e) {
            } catch (Exception e) {
                this.localLock.unlock();
                rethrowAsLockException(e);
            }
        }
    }
}</code>

Note: Both database‑based and Redis‑based locks require acquiring a local lock first.

Spring Cloud Task uses the database‑based lock from Spring Integration.

Conclusion: Spring Integration’s distributed lock provides a reliable synchronization method for distributed systems. By selecting the appropriate implementation (database or Redis) and configuring it correctly, developers can ensure coordinated access to shared resources, improving overall system reliability and performance.

Finished!!

JavaSpring BootDistributed LockSpring IntegrationJdbcLockRedisLock
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login 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.