Knowledge Base--The Write Skew Anomaly Fixed By Akka(136)

8154 ワード

/**  1000 
 In Handling Write Skew Anomaly, we discussed write skew and how Clojure STM handles it. Akka also has support for dealing with write skew, *but we have to configure it*. OK, that word may sound scary, but it’s really simple. Let’s first see the default behavior without any configuration. Let’s revisit the example of multiple accounts with a restricted combined balance that we looked at earlier. Create a Portfolio class that holds a checking account balance and a savings account balance. These two accounts have a constraint of the total balance not running less than $1,000. This class along with the withdraw() method is shown next. In this method, we obtain the two balances first, compute their total, and after a intentional delay (introduced to set transactions on collision course) we subtract the given amount from either the checking balance or the savings balance if the total is not less than $1,000. The withdraw() method does its operations within a transaction configured using default settings.
 */

public class Portfolio {
    final private Ref checkingBalance = new Ref(500);
    final private Ref savingsBalance = new Ref(600);

    public int getCheckingBalance() { return checkingBalance.get(); }
    public int getSavingsBalance() { return savingsBalance.get(); }

    public void withdraw(final boolean fromChecking, final int amount) {
        new Atomic() {
           public Object atomically() {
                final int totalBalance =checkingBalance.get() + savingsBalance.get();
                try { Thread.sleep(1000); } catch(InterruptedException ex) {}
                if(totalBalance - amount >= 1000) {
                    if(fromChecking)
                        checkingBalance.swap(checkingBalance.get() - amount);
                    else
                    savingsBalance.swap(savingsBalance.get() - amount);
                    }
                else
                System.out.println(
                         "Sorry, can't withdraw due to constraint violation");
                 return null;
                }
             }.execute();
         }
     }

//test
    public class UsePortfolio {
        public static void main(final String[] args) throws InterruptedException {
            final Portfolio portfolio = new Portfolio();
            //init
            int checkingBalance = portfolio.getCheckingBalance();
            int savingBalance = portfolio.getSavingsBalance();
            System.out.println("Checking balance is " + checkingBalance);
            System.out.println("Savings balance is " + savingBalance);
            System.out.println("Total balance is " +
                    (checkingBalance + savingBalance));

            final ExecutorService service = Executors.newFixedThreadPool(10);
            service.execute(new Runnable() {
                public void run() { portfolio.withdraw(true, 100); }
            });
            service.execute(new Runnable() {
                public void run() { portfolio.withdraw(false, 100); }
            });
            service.shutdown();
            Thread.sleep(4000);
            checkingBalance = portfolio.getCheckingBalance();

            savingBalance = portfolio.getSavingsBalance();
            System.out.println("Checking balance is " + checkingBalance);
            System.out.println("Savings balance is " + savingBalance);
            System.out.println("Total balance is " +
                    (checkingBalance + savingBalance));
            if(checkingBalance + savingBalance < 1000)
                System.out.println("Oops, broke the constraint!");
        }
    }

解析:By default,Akka does not avoid write skew,and the two transactions will proceed running the balance into constraint violation,as we see in the output:結果1:
     Checking balance is 500
     Savings balance is 600
     Total balance is 1100
     Checking balance is 400
     Savings balance is 500
     Total balance is 900
     Oops, broke the constraint!

FIX:
  new Atomic() {
        to the following:
        akka.stm.TransactionFactory factory =
                new akka.stm.TransactionFactoryBuilder()
                        .setWriteSkew(false)
                        .setTrackReads(true)
                        .build();
        new Atomic(factory) {...

We created a TransactionFactoryBuilder and set the writeSkew and trackReads properties to false and true, respectively. This tells the transaction to keep track of reads within a transaction and also to set a read lock on the reads until the commit begins, just as Clojure STM handles ensure.