Advertisement
Guest User

Untitled

a guest
Dec 16th, 2018
86
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Java 12.16 KB | None | 0 0
  1. package ru.ifmo.pp;
  2.  
  3. import java.util.concurrent.atomic.AtomicReferenceArray;
  4.  
  5. /**
  6.  * Bank implementation.
  7.  * This class is thread-safe and lock-free using operation objects.
  8.  *
  9.  * <p>This implementation is based on "A Practical Multi-Word Compare-and-Swap Operation"  by T. L. Harris et al.
  10.  * It uses a simplified and faster version of DCSS operation that relies for its correctness on the fact that
  11.  * Account instances in {@link #accounts} array never suffer from ABA problem.
  12.  * See also "Practical lock-freedom" by Keir Fraser.
  13.  * See {@link #acquire(int, Op)} method.
  14.  */
  15. public class BankImpl implements Bank {
  16.     /**
  17.      * An array of accounts by index.
  18.      * Account instances here are never reused (there is no ABA).
  19.      */
  20.     private final AtomicReferenceArray<Account> accounts;
  21.  
  22.     /**
  23.      * Creates new bank instance.
  24.      *
  25.      * @param n the number of accounts (numbered from 0 to n-1).
  26.      */
  27.     public BankImpl(int n) {
  28.         accounts = new AtomicReferenceArray<>(n);
  29.         for (int i = 0; i < n; i++) {
  30.             accounts.set(i, new Account(0));
  31.         }
  32.     }
  33.  
  34.     /**
  35.      * {@inheritDoc}
  36.      */
  37.     @Override
  38.     public int getNumberOfAccounts() {
  39.         return accounts.length();
  40.     }
  41.  
  42.     /**
  43.      * {@inheritDoc}
  44.      */
  45.     @Override
  46.     public long getAmount(int index) {
  47.         while (true) {
  48.             Account account = accounts.get(index);
  49.             /*
  50.              * If there is a pending operation on this account, then help to complete it first using
  51.              * its invokeOperation method. If the result is false then there is no pending operation,
  52.              * thus the account amount can be safely returned.
  53.              */
  54.             if (!account.invokeOperation()) {
  55.                 return account.amount;
  56.             }
  57.         }
  58.     }
  59.  
  60.     /**
  61.      * {@inheritDoc}
  62.      */
  63.     @Override
  64.     public long getTotalAmount() {
  65.         /**
  66.          * This operation requires atomic read of all accounts, thus it creates an operation descriptor.
  67.          * Operation's invokeOperation method acquires all accounts, computes the total amount, and releases
  68.          * all accounts. This method returns the result.
  69.          */
  70.         TotalAmountOp op = new TotalAmountOp();
  71.         op.invokeOperation();
  72.         return op.sum;
  73.     }
  74.  
  75.     /**
  76.      * {@inheritDoc}
  77.      */
  78.     @Override
  79.     public long deposit(int index, long amount) {
  80.         // First, validate method per-conditions
  81.         if (amount <= 0) {
  82.             throw new IllegalArgumentException("Invalid amount: " + amount);
  83.         }
  84.         if (amount > MAX_AMOUNT) {
  85.             throw new IllegalStateException("Overflow");
  86.         }
  87.         /*
  88.          * This operation depends only on a single account, thus it can be directly
  89.          * performed using a regular lock-free compareAndSet loop.
  90.          */
  91.         while (true) {
  92.             Account account = accounts.get(index);
  93.             /*
  94.              * If there is a pending operation on this account, then help to complete it first using
  95.              * its invokeOperation method. If the result is false then there is no pending operation,
  96.              * thus the account can be safely updated.
  97.              */
  98.             if (!account.invokeOperation()) {
  99.                 if (account.amount + amount > MAX_AMOUNT) {
  100.                     throw new IllegalStateException("Overflow");
  101.                 }
  102.                 Account updated = new Account(account.amount + amount);
  103.                 if (accounts.compareAndSet(index, account, updated)) {
  104.                     return updated.amount;
  105.                 }
  106.             }
  107.         }
  108.     }
  109.  
  110.     /**
  111.      * {@inheritDoc}
  112.      */
  113.     @Override
  114.     public long withdraw(int index, long amount) {
  115.         if (amount <= 0) {
  116.             throw new IllegalArgumentException("Invalid amount: " + amount);
  117.         }
  118.         while (true) {
  119.             Account account = accounts.get(index);
  120.             if (!account.invokeOperation()) {
  121.                 if (account.amount - amount < 0) {
  122.                     throw new IllegalStateException("Underflow");
  123.                 }
  124.                 Account updated = new Account(account.amount - amount);
  125.                 if (accounts.compareAndSet(index, account, updated)) {
  126.                     return updated.amount;
  127.                 }
  128.             }
  129.         }
  130.     }
  131.  
  132.     /**
  133.      * {@inheritDoc}
  134.      */
  135.     @Override
  136.     public void transfer(int fromIndex, int toIndex, long amount) {
  137.         // First, validate method per-conditions
  138.         if (amount <= 0)
  139.             throw new IllegalArgumentException("Invalid amount: " + amount);
  140.         if (fromIndex == toIndex)
  141.             throw new IllegalArgumentException("fromIndex == toIndex");
  142.         if (amount > MAX_AMOUNT)
  143.             throw new IllegalStateException("Underflow/overflow");
  144.         /**
  145.          * This operation requires atomic read of two accounts, thus it creates an operation descriptor.
  146.          * Operation's invokeOperation method acquires both accounts, computes the result of operation
  147.          * (if a form of error message), and releases both accounts. This method throws the exception with
  148.          * the corresponding message if needed.
  149.          */
  150.         TransferOp op = new TransferOp(fromIndex, toIndex, amount);
  151.         op.invokeOperation();
  152.         if (op.errorMessage != null) {
  153.             throw new IllegalStateException(op.errorMessage);
  154.         }
  155.     }
  156.  
  157.  
  158.     /**
  159.      * This is an implementation of a restricted form of Harris DCSS operation:
  160.      * It atomically checks that op.completed is false and replaces accounts[index] with AcquiredAccount instance
  161.      * that hold a reference to the op.
  162.      * This method returns null if op.completed is true.
  163.      */
  164.     private AcquiredAccount acquire(int index, Op op) {
  165.         while (true) {
  166.             if (op.completed) {
  167.                 return null;
  168.             }
  169.             Account account = accounts.get(index);
  170.             if (account instanceof AcquiredAccount) {
  171.                 AcquiredAccount acquiredAccount = (AcquiredAccount) account;
  172.                 if (op == acquiredAccount.op) {
  173.                     return acquiredAccount;
  174.                 } else {
  175.                     acquiredAccount.invokeOperation();
  176.                 }
  177.             } else {
  178.                 if (op.completed) {
  179.                     return null;
  180.                 }
  181.                 AcquiredAccount acquiredAccount = new AcquiredAccount(account.amount, op);
  182.                 if (accounts.compareAndSet(index, account, acquiredAccount)) {
  183.                     return acquiredAccount;
  184.                 }
  185.             }
  186.         }
  187.     }
  188.  
  189.     /**
  190.      * Releases an account that was previously acquired by {@link #acquire(int, Op)}.
  191.      * This method does nothing if the account at index is not currently acquired.
  192.      */
  193.     private void release(int index, Op op) {
  194.         assert op.completed; // must be called only on operations that were already completed
  195.         Account account = accounts.get(index);
  196.         if (account instanceof AcquiredAccount) {
  197.             AcquiredAccount acquiredAccount = (AcquiredAccount) account;
  198.             if (acquiredAccount.op == op) {
  199.                 // release performs update at most once while the account is still acquired
  200.                 Account updated = new Account(acquiredAccount.newAmount);
  201.                 accounts.compareAndSet(index, account, updated);
  202.             }
  203.         }
  204.     }
  205.  
  206.     /**
  207.      * Immutable account data structure.
  208.      */
  209.     private static class Account {
  210.         /**
  211.          * Amount of funds in this account.
  212.          */
  213.         final long amount;
  214.  
  215.         Account(long amount) {
  216.             this.amount = amount;
  217.         }
  218.  
  219.         /**
  220.          * Invokes operation that is pending on this account.
  221.          * This implementation returns false (no pending operation),
  222.          * other implementations return true.
  223.          */
  224.         boolean invokeOperation() {
  225.             return false;
  226.         }
  227.     }
  228.  
  229.     /**
  230.      * Account that was acquired as a part of in-progress operation that spans multiple accounts.
  231.      *
  232.      * @see #acquire(int, Op)
  233.      */
  234.     private static class AcquiredAccount extends Account {
  235.         final Op op;
  236.  
  237.         /**
  238.          * New amount of funds in this account when op completes.
  239.          */
  240.         long newAmount;
  241.  
  242.         AcquiredAccount(long amount, Op op) {
  243.             super(amount);
  244.             this.op = op;
  245.             this.newAmount = amount;
  246.         }
  247.  
  248.         @Override
  249.         boolean invokeOperation() {
  250.             op.invokeOperation();
  251.             return true;
  252.         }
  253.     }
  254.  
  255.     /**
  256.      * Abstract operation that acts on multiple accounts.
  257.      */
  258.     private abstract class Op {
  259.         /**
  260.          * True when operation has completed.
  261.          */
  262.         volatile boolean completed;
  263.  
  264.         abstract void invokeOperation();
  265.     }
  266.  
  267.     /**
  268.      * Descriptor for {@link #getTotalAmount()} operation.
  269.      */
  270.     private class TotalAmountOp extends Op {
  271.         /**
  272.          * The result of getTotalAmount operation is stored here before setting
  273.          * {@link #completed} to true.
  274.          */
  275.         long sum;
  276.  
  277.         @Override
  278.         void invokeOperation() {
  279.             long sum = 0;
  280.             int i;
  281.             int n = accounts.length();
  282.             for (i = 0; i < n; i++) {
  283.                 AcquiredAccount account = acquire(i, this);
  284.                 if (account == null)
  285.                     break;
  286.                 sum += account.amount;
  287.             }
  288.             if (i == n) {
  289.                 /*
  290.                  * If i == n, then all acquired accounts were not null and full sum was calculated.
  291.                  * this.sum = sum assignment below has a benign data race. Multiple threads might to this assignment
  292.                  * concurrently, however, they are all guaranteed to be assigning the same value.
  293.                  */
  294.                 this.sum = sum;
  295.                 this.completed = true; // volatile write to completed field _after_ the sum was written
  296.             }
  297.             /*
  298.              * As performance optimization, only acquired accounts are released. There is no harm in calling
  299.              * release for all accounts, though.
  300.              */
  301.             for (; --i >= 0; ) {
  302.                 release(i, this);
  303.             }
  304.         }
  305.     }
  306.  
  307.     /**
  308.      * Descriptor for {@link #transfer(int, int, long) transfer(...)} operation.
  309.      */
  310.     private class TransferOp extends Op {
  311.         final int fromIndex;
  312.         final int toIndex;
  313.         final long amount;
  314.  
  315.         String errorMessage;
  316.  
  317.         TransferOp(int fromIndex, int toIndex, long amount) {
  318.             this.fromIndex = Math.min(fromIndex, toIndex);
  319.             this.toIndex = Math.max(fromIndex, toIndex);
  320.             this.amount = (this.fromIndex == fromIndex) ? amount : -amount;
  321.         }
  322.  
  323.         @Override
  324.         void invokeOperation() {
  325.             int[] indices = {fromIndex, toIndex};
  326.             AcquiredAccount[] acquired = {null, null};
  327.             int i = 0;
  328.             for (; i < indices.length; ++i) {
  329.                 AcquiredAccount account = acquire(indices[i], this);
  330.                 if (account == null) {
  331.                     break;
  332.                 } else {
  333.                     acquired[i] = account;
  334.                 }
  335.             }
  336.             if (i == indices.length) {
  337.                 if (acquired[0].amount - amount < 0) {
  338.                     errorMessage = "Underflow";
  339.                 } else if (acquired[0].amount - amount > MAX_AMOUNT) {
  340.                     errorMessage = "Overflow";
  341.                 } else if (acquired[1].amount + amount < 0) {
  342.                     errorMessage = "Underflow";
  343.                 } else if (acquired[1].amount + amount > MAX_AMOUNT) {
  344.                     errorMessage = "Overflow";
  345.                 } else {
  346.                     acquired[0].newAmount = acquired[0].amount - amount;
  347.                     acquired[1].newAmount = acquired[1].amount + amount;
  348.                 }
  349.                 completed = true;
  350.             }
  351.             for (; --i >= 0; ) {
  352.                 release(indices[i], this);
  353.             }
  354.         }
  355.     }
  356. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement