多线程并发总结四 STM

如果我们要处理的情况符合下面的几种之一,我们可以考虑一下使用非阻塞的方式来处理并发的情况。

  • 有多线程的情况要处理,但是很少出现并发冲突的,比如以读取为主的
  • 涉及到的并发逻辑非常复杂,希望可以减低并发代码出错的可能和保持并发代码的可读性。

非阻塞的代码可以较大限度的保持并发代码的可读性。它的原理就像下面的伪代码

AtomicInteger ai = new AtomicInteger(1);

new Thread(() -> {
    do {
        int oldValue = ai.getValue();
        int newValue = doSomeCalculate(oldValue);
    } while (ai.checkAndUpdate(oldValue, newValue));
});

代码里面没有任何的互斥的情况,如果没有出现大量的冲突而需要重试的情况,这段代码的执行效率将会十分的高。

但是这种处理有其限制,就是它只允许有一个的变量是共享的。

下面的伪代码是有问题的。

AtomicInteger ai1 = new AtomicInteger(1);
AtomicInteger ai2 = new AtomicInteger(1);

new Thread(() -> {
    do {
        int oldValue1 = ai1.getValue();
        int oldValue2 = ai2.getValue();
        int newValue1 = doSomeCalculate(oldValue1);
        int newValue2 = doSomeCalculate(oldValue2);
    } while (ai1.checkAndUpdate(oldValue1, newValue1) &&
             ai2.checkAndUpdate(oldValue2, newValue2)   );
});

原因就是两次取数不是原子操作,两次的checkAndUpdate也不是原子操作。

当然我们可以引入框架,把类似两段提交这样的操作引入来。

这样的实现,我们叫做STM,Software Transaction Memory。

在Java的世界里面,关于STM的实现有好几个,其中包括JVSTM(Java Version STM) 或者 Multiverse

import org.multiverse.api.references.*;
import static org.multiverse.api.StmUtils.*;

public class Account{
    private final TxnRef lastModified = new TxnRef();
    private final TxnLong amount = new TxnLong();

    public Account(long amount){
       this.amount.set(amount);
       this.lastModified.set(new Date());
    }

    public Date getLastModifiedDate(){
        return lastModified.get();
    }

    public long getAmount(){
        return amount.get();
    }

    public static void transfer(final Account from, final Account to, final long amount){
        atomic(new Runnable()){
            public void run(){
                Date date = new Date();

                from.lastModified.set(date);
                from.amount.dec(amount);

                to.lastModified.set(date);
                to.amount.inc(amount);
            }
        }
    }
}

# And it can be called like this:

Account account1 = new Account(10);
Account account2 = new Account(20)
Account.transfer(account1, account2, 5);

上面是Multiverse的例子,所有的那些需要共享的变量都用框架提供的类型,比如TxnLong,TxnRef。

然后使用关键词atomic来接收Runnable来起线程,这样就可以确保里面的共享变量最后执行的结果不会收到多线程的影响了。

STM主要还是通过非阻塞的方式来实现,如果是很少发生冲突的,其执行效率将会很高。但是如果是会经常发生冲突的话,就会经常需要重跑逻辑,可能会影响了最终的执行效率。

你可能感兴趣的:(多线程和并发)