Skip to content

Instantly share code, notes, and snippets.

@VincentTatan
Created October 21, 2015 06:52
Show Gist options
  • Save VincentTatan/eca472d1d77dcc9ce723 to your computer and use it in GitHub Desktop.
Save VincentTatan/eca472d1d77dcc9ce723 to your computer and use it in GitHub Desktop.
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.io.*;
/**
* The Account class:
* 1) has a current balance and a name
* 2) allows transfers to another account
*
* Important properties:
* 1) The Account class is not thread safe. This means that running multiple transfers (from or to) the same account
* concurrently may result in unexpected outcomes (such as less debit or less credit than expected).
*
* Important options:
* 1) SLOW - there is a static variable "SLOW" which determines how much processor time transfers take. When set
* to true, the computational load of a transfer is high. When set to false, the load will be low. Setting to
* true will increase the performance impact of parallelism. Setting to false will increase the likelihood of
* race conditions.
*
* 2) onDisk - object variable determines if the balance is saved to (and read from) disk on each action.
*/
public class Account
{
private static Lock lock = new ReentrantLock();
public static boolean SLOW = false;
private boolean onDisk = false;
public Account(float balance, String name, boolean onDisk)
{
this.balance = balance;
this.name = name;
this.onDisk = onDisk;
if (onDisk) saveToDisk();
}
/**
* This versions requires that the balance is never negative. This is sensible in that
* Amy should never be allowed to transfer more money to Betty than Amy actually has.
* If transfers can interleave, then two transfers from Amy can both pass the if statement
* even if there is only enough money for one of them.
*
*/
public void transfer(float amount, Account to)
{
if (amount < balance)
{
Thread.yield(); //this just makes the multithreading issues more common
this.debit(amount);
to.credit(amount);
if (SLOW) complicatedComputation();
}
if (balance < 0)
{
System.out.println("Negative balance detected: balance = " + balance);
}
}
/**
* to make a problem more likely, add either of the lines below in between 1 & 2:
* Thread.yield();
* System.out.println("got balance: " + balance);
*
* to make problems less likely try with just:
* balance -= amount;
* then run a few times. you (may) see that though less common, problems still happen!
*/
private void debit (float amount)
{
lock.lock();
float balance = getBalance(); //1
balance = balance - amount; //2
saveBalance(balance); //3
lock.unlock();
}
private void credit(float amount)
{
lock.lock();
float balance = getBalance();
balance = balance + amount;
saveBalance(balance);
lock.unlock();
}
//------------------------------------------------------------
private float balance;
private String name;
public float getBalance()
{
if (onDisk)
return readFromDisk();
else
return balance;
}
/**
* saves balance to storage, in this case just "this.balance", but in practice could
* be a DB, another system, a file, ...
*/
private void saveBalance(float amount)
{
this.balance = amount;
if (onDisk) saveToDisk();
}
private void complicatedComputation()
{
float j = 1;
for (long i = 0; i < 110000; i++) { j += i % 15; }
}
private void saveToDisk()
{
PrintWriter writer = null;
try{
writer = new PrintWriter(new BufferedWriter(new FileWriter(name + ".txt", false)));
writer.println(balance);
writer.flush();
writer.close();
}catch(Exception e){
System.err.println(e.getMessage());
e.printStackTrace();
}
}
private float readFromDisk()
{
BufferedReader br = null;
String s = null;
try{
br = new BufferedReader(new FileReader(name + ".txt"));
s = br.readLine();
br.close();
}catch (IOException e){
System.err.println(e.getMessage());
e.printStackTrace();
}
try
{
float b = Float.parseFloat(s);
return b;
} catch (Exception e) {
return balance;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment