Skip to content

mtumilowicz/java11-concurrency-lock-execute-around-method-pattern

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Build Status

java11-concurrency-lock-execute-around-method-pattern

Example of Execute Around Method pattern.

preface

For details about ReadWriteLock please refer my other github project: https://github.com/mtumilowicz/java-concurrency-readwritelock

In this project we will show how to use Execute Around Method (EAM) pattern to wrap critical sections and minimise the pain of working with the ReadWriteLock interface.

project description

The main goal: simple implementation of transferring money from user to user.

  1. Immutable User class
    @Value
    class User {
        int id;
        int balance;
    
        User income(PositiveInt value) {
            return new User(id, balance + value.amount);
        }
    
        User outcome(PositiveInt value) {
            return new User(id, balance - value.amount);
        }
    }
    
    class PositiveInt {
        final int amount;
    
        private PositiveInt(int amount) {
            this.amount = amount;
        }
    
        static PositiveInt of(int amount) {
            Preconditions.checkArgument(amount > 0);
    
            return new PositiveInt(amount);
        }
    }
    
  2. Mutable map of users:
    userMap = [[1, new User(1, 40)], [2, new User(2, 33)]]
    
  3. We want to provide tread-safe read and writes on that map
    • we will use ReadWriteLock interface
    • we provide class that absorbs the pain of working with that interface (locking/unlocking tiresome obligation)
    • we will supply action to execute in the locked block
    @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE)
    @RequiredArgsConstructor
    class LockExecutor {
        ReadWriteLock lock;
    
        <T> T write(Supplier<T> action) {
            lock.writeLock().lock();
            try {
                return action.get();
            } finally {
                lock.writeLock().unlock();
            }
        }
    
        <T> T read(Supplier<T> action) {
            lock.readLock().lock();
            try {
                return action.get();
            } finally {
                lock.readLock().unlock();
            }
        }
    }
    
    Remark: we choose Supplier as a parameter, because it's often very handy to return a value from that methods.

tests

  1. We want to transfer 15 credits from the first user, to the second one
    ReadWriteLock lock = new ReentrantReadWriteLock();
    LockExecutor executor = new LockExecutor(lock);
    
    executor.write(() -> {
        var transfer = PositiveInt.of(15);
        userMap.replace(1, userMap.get(1).outcome(transfer));
        userMap.replace(2, userMap.get(2).income(transfer));
    
        return Void.class;
    });
    
    assertThat(userMap.get(1).getBalance(), is(25));
    assertThat(userMap.get(2).getBalance(), is(48));
    
  2. We want to sum all balances from all users
    ReadWriteLock lock = new ReentrantReadWriteLock();
    LockExecutor executor = new LockExecutor(lock);
    
    var balanceAll = executor.read(() -> userMap.values()
            .stream()
            .map(User::getBalance)
            .mapToInt(x -> x)
            .sum());
    
    assertThat(balanceAll, is(73));
    

Remark: thread-safety is guarantee under assumption, that all reads and writes will go through the same executor object.

Releases

No releases published

Packages

No packages published

Languages