1. 概述

本文将探讨Multiverse库——一个帮助我们在Java中实现软件事务内存(STM)概念的强大工具。通过该库提供的构造,我们可以为共享状态创建同步机制,这比使用Java核心库的标准实现更优雅、更易读。

2. Maven依赖

首先需要在pom.xml中添加multiverse-core依赖:

<dependency>
    <groupId>org.multiverse</groupId>
    <artifactId>multiverse-core</artifactId>
    <version>0.7.0</version>
</dependency>

3. Multiverse API核心概念

3.1 STM基础原理

软件事务内存(STM)是从SQL数据库世界移植的概念——每个操作都在满足ACID(原子性、一致性、隔离性、持久性)属性的事务中执行。⚠️ 这里只满足原子性、一致性和隔离性,因为机制运行在内存中。

3.2 核心接口

Multiverse库的核心接口是 TxnObject ——每个事务对象都需要实现它,库提供了多个可直接使用的子类。

3.3 事务执行规则

✅ 需要放在临界区(critical section)的操作(只能由一个线程访问且使用事务对象)必须包装在StmUtils.atomic()方法中。 ✅ 事务成功则提交,新状态对其他线程可见 ❌ 出错则不提交,状态保持不变 ⚠️ 当两个线程同时修改同一状态时,只有一个能成功提交,另一个将在其事务中重试操作

4. 使用STM实现账户逻辑

4.1 账户类设计

假设我们要用Multiverse的STM实现银行账户逻辑。Account对象包含:

  • lastUpdateTxnLong类型的时间戳
  • balanceTxnInteger类型的账户余额

TxnLongTxnInteger是Multiverse提供的类,必须在事务中执行,否则会抛出异常。使用StmUtils创建事务对象实例:

public class Account {
    private TxnLong lastUpdate;
    private TxnInteger balance;

    public Account(int balance) {
        this.lastUpdate = StmUtils.newTxnLong(System.currentTimeMillis());
        this.balance = StmUtils.newTxnInteger(balance);
    }
}

4.2 余额调整方法

实现adjustBy()方法增加余额,该操作必须在事务中执行。如果抛出异常,事务将回滚:

public void adjustBy(int amount) {
    adjustBy(amount, System.currentTimeMillis());
}

public void adjustBy(int amount, long date) {
    StmUtils.atomic(() -> {
        balance.increment(amount);
        lastUpdate.set(date);

        if (balance.get() <= 0) {
            throw new IllegalArgumentException("余额不足");
        }
    });
}

4.3 余额查询方法

获取账户余额也需要原子语义:

public Integer getBalance() {
    return balance.atomicGet();
}

5. 账户功能测试

5.1 基础扣款测试

简单扣款操作验证:

@Test
public void givenAccount_whenDecrement_thenShouldReturnProperValue() {
    Account a = new Account(10);
    a.adjustBy(-5);

    assertThat(a.getBalance()).isEqualTo(5);
}

5.2 异常回滚测试

扣款导致余额为负时,事务应回滚:

@Test(expected = IllegalArgumentException.class)
public void givenAccount_whenDecrementTooMuch_thenShouldThrow() {
    // given
    Account a = new Account(10);

    // when
    a.adjustBy(-11);
}

5.3 并发冲突测试

两个线程同时扣款(初始余额10):

  • 线程1扣款6
  • 线程2扣款5

使用ExecutorServiceCountDownLatch模拟并发:

ExecutorService ex = Executors.newFixedThreadPool(2);
Account a = new Account(10);
CountDownLatch countDownLatch = new CountDownLatch(1);
AtomicBoolean exceptionThrown = new AtomicBoolean(false);

ex.submit(() -> {
    try {
        countDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    try {
        a.adjustBy(-6);
    } catch (IllegalArgumentException e) {
        exceptionThrown.set(true);
    }
});
ex.submit(() -> {
    try {
        countDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    try {
        a.adjustBy(-5);
    } catch (IllegalArgumentException e) {
        exceptionThrown.set(true);
    }
});

验证必有且仅有一个操作失败:

countDownLatch.countDown();
ex.awaitTermination(1, TimeUnit.SECONDS);
ex.shutdown();

assertTrue(exceptionThrown.get());

6. 账户间转账实现

6.1 转账方法

Account类中实现transferTo()方法:

public void transferTo(Account other, int amount) {
    StmUtils.atomic(() -> {
        long date = System.currentTimeMillis();
        adjustBy(-amount, date);
        other.adjustBy(amount, date);
    });
}

✅ 所有操作在单一事务中执行,保证原子性 ❌ 转账金额超过余额时,整个事务回滚

6.2 正常转账测试

Account a = new Account(10);
Account b = new Account(10);

a.transferTo(b, 5);

assertThat(a.getBalance()).isEqualTo(5);
assertThat(b.getBalance()).isEqualTo(15);

6.3 异常转账测试

try {
    a.transferTo(b, 20);
} catch (IllegalArgumentException e) {
    System.out.println("转账失败");
}

assertThat(a.getBalance()).isEqualTo(5);
assertThat(b.getBalance()).isEqualTo(15);

7. STM的死锁安全性

7.1 传统同步的死锁问题

使用标准Java同步机制时,转账操作容易死锁:

  • 线程1:锁定账户a → 尝试锁定账户b
  • 线程2:锁定账户b → 尝试锁定账户a ⚠️ 结果:程序无限阻塞

7.2 STM的解决方案

STM天然死锁安全!使用transferTo()方法测试并发转账:

ExecutorService ex = Executors.newFixedThreadPool(2);
Account a = new Account(10);
Account b = new Account(10);
CountDownLatch countDownLatch = new CountDownLatch(1);

ex.submit(() -> {
    try {
        countDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    a.transferTo(b, 10);
});
ex.submit(() -> {
    try {
        countDownLatch.await();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    b.transferTo(a, 1);
});

验证最终状态正确(无死锁):

countDownLatch.countDown();
ex.awaitTermination(1, TimeUnit.SECONDS);
ex.shutdown();

assertThat(a.getBalance()).isEqualTo(1);
assertThat(b.getBalance()).isEqualTo(19);

8. 总结

本文深入探讨了Multiverse库及其在Java中实现无锁、线程安全的软件事务内存(STM)方案。通过实际案例验证了: ✅ STM的原子性保证 ✅ 事务回滚机制 ✅ 并发冲突处理 ✅ 死锁安全性

所有示例代码可在GitHub项目中获取(Maven项目,可直接导入运行)。对于需要处理复杂共享状态同步的场景,STM提供了一种比传统锁机制更优雅、更安全的解决方案。


原始标题:Software Transactional Memory in Java Using Multiverse

» 下一篇: HikariCP 介绍