以原子方式将新字符连接到 StringBuilder 对象

Concat new characters to StringBuilder object atomically

我的问题是:

我有 class:

public class AtomicStringBuilder {
    private final AtomicReference<StringBuilder> sbRef;
}

我需要以原子方式并发地向 StringBuilder 添加新字符。但问题是,这个对象中应该只有最后 128 个字符。我不能使用 StringBuffer,因为操作应该是非阻塞的。

所以,有两个操作:

首先:检查StringBuilder是否已经有128个字符。

第二:如果没有 -> 添加新字符,如果有 -> 删除第一个字符并添加新字符。

有没有办法让这两个或三个操作成为原子操作?

我做了这个方法,但是不行:

public void append(String string) {
        this.sbRef.getAndUpdate(ref -> {
            if (ref.length() < 128) {
                ref.append(string);
            } else {
                ref.append(string).delete(0, ref.length() - 128);
            }
            return ref;
        });
    }

为了测试我创建了这个方法:

public void test() {
AtomicStringBuilder atomicStringBuilder = new AtomicStringBuilder();
Random random = new Random();
Stream<Integer> infiniteStream = Stream.iterate(0, i -> random.nextInt(10));

infiniteStream.parallel()
.limit(100000)
.forEach(integer -> atomicStringBuilder.append(String.valueOf(integer)));

assertEquals(128, atomicStringBuilder.getSb().get().length());
}

这不是真正的问题,我可以将 AtomicReference 更改为任何其他可行的方法。任务是创建无锁且没有竞争条件的操作

这是一个使用不可变字符串的解决方案。

如果您使用 AtomicReference,您需要 return 一个新的引用,而不是改变引用指向的对象。原子地比较引用的当前值和期望值是知道它没有被另一个线程更新的唯一方法。

getAndUpdate 这样做:

  1. 获取当前引用
  2. 将 lambda 应用于引用,获取新引用
  3. 如果当前引用没有改变,自动将其设置为新引用,否则返回 1。
public class App {
    static class AtomicStringBuilder {
        public final AtomicInteger counter = new AtomicInteger();

        public final AtomicReference<String> sbRef = new AtomicReference<>("");

        public void append(String string) {
            this.sbRef.getAndUpdate(ref -> {
                counter.getAndIncrement();
                if (ref.length() < 128) {
                    return ref + string;
                } else {
                    String s = ref + string;
                    return s.substring(s.length() - 128);
                }
            });
        }
    }

    static void test() {
        AtomicStringBuilder atomicStringBuilder = new AtomicStringBuilder();
        Random random = new Random();
        Stream<Integer> infiniteStream = Stream.iterate(0, i -> random.nextInt(10));

        infiniteStream.parallel()
                .limit(100000)
                .forEach(integer -> atomicStringBuilder.append(String.valueOf(integer)));

        if (128 != atomicStringBuilder.sbRef.get().length()) {
            System.out.println("failed ");
        }
        System.out.println(atomicStringBuilder.sbRef.get());
        System.out.println(atomicStringBuilder.counter.get());
    }

    public static void main(String[] args) {
        test();
    }
}

我已经为 lambda 添加了一个计数器。 运行这个程序后显示的值会超过100,000,因为并发更新强制重试。