OnJava8-Examples/threads/BankTellerSimulation.java

219 lines
6.6 KiB
Java
Raw Normal View History

2016-07-05 14:46:09 -06:00
// threads/BankTellerSimulation.java
2015-12-15 11:47:04 -08:00
// (c)2016 MindView LLC: see Copyright.txt
2015-11-15 15:51:35 -08:00
// We make no guarantees that this code is fit for any purpose.
// Visit http://mindviewinc.com/Books/OnJava/ for more book information.
2016-01-25 18:05:55 -08:00
// Using queues and multithreading
2016-07-28 13:42:03 -06:00
// {java BankTellerSimulation 5}
2015-06-15 17:47:35 -07:00
import java.util.concurrent.*;
import java.util.*;
// Read-only objects don't require synchronization:
class Customer {
private final int serviceTime;
public Customer(int tm) { serviceTime = tm; }
public int getServiceTime() { return serviceTime; }
public String toString() {
return "[" + serviceTime + "]";
}
}
// Teach the customer line to display itself:
class CustomerLine extends ArrayBlockingQueue<Customer> {
public CustomerLine(int maxLineSize) {
super(maxLineSize);
}
@Override
public String toString() {
if(this.size() == 0)
return "[Empty]";
StringBuilder result = new StringBuilder();
for(Customer customer : this)
result.append(customer);
return result.toString();
}
}
// Randomly add customers to a queue:
2015-11-03 12:00:44 -08:00
class CustomerSupplier implements Runnable {
2015-06-15 17:47:35 -07:00
private CustomerLine customers;
2016-01-25 18:05:55 -08:00
private static SplittableRandom rand = new SplittableRandom(47);
2015-11-03 12:00:44 -08:00
public CustomerSupplier(CustomerLine cq) {
2015-06-15 17:47:35 -07:00
customers = cq;
}
@Override
public void run() {
try {
while(!Thread.interrupted()) {
TimeUnit.MILLISECONDS.sleep(rand.nextInt(300));
customers.put(new Customer(rand.nextInt(1000)));
}
} catch(InterruptedException e) {
2015-11-03 12:00:44 -08:00
System.out.println("CustomerSupplier interrupted");
2015-06-15 17:47:35 -07:00
}
2015-11-03 12:00:44 -08:00
System.out.println("CustomerSupplier terminating");
2015-06-15 17:47:35 -07:00
}
}
class Teller implements Runnable, Comparable<Teller> {
private static int counter = 0;
private final int id = counter++;
// Customers served during this shift:
private int customersServed = 0;
private CustomerLine customers;
private boolean servingCustomerLine = true;
public Teller(CustomerLine cq) { customers = cq; }
public void run() {
try {
while(!Thread.interrupted()) {
Customer customer = customers.take();
TimeUnit.MILLISECONDS.sleep(
customer.getServiceTime());
synchronized(this) {
customersServed++;
while(!servingCustomerLine)
wait();
}
}
} catch(InterruptedException e) {
System.out.println(this + "interrupted");
}
System.out.println(this + "terminating");
}
public synchronized void doSomethingElse() {
customersServed = 0;
servingCustomerLine = false;
}
public synchronized void serveCustomerLine() {
2016-01-25 18:05:55 -08:00
assert !servingCustomerLine:
"already serving: " + this;
2015-06-15 17:47:35 -07:00
servingCustomerLine = true;
notifyAll();
}
2016-01-25 18:05:55 -08:00
public String toString() {
return "Teller " + id + " ";
}
2015-06-15 17:47:35 -07:00
public String shortString() { return "T" + id; }
// Used by priority queue:
public synchronized int compareTo(Teller other) {
return customersServed < other.customersServed ? -1 :
(customersServed == other.customersServed ? 0 : 1);
}
}
class TellerManager implements Runnable {
private ExecutorService exec;
private CustomerLine customers;
private PriorityQueue<Teller> workingTellers =
new PriorityQueue<>();
private Queue<Teller> tellersDoingOtherThings =
new LinkedList<>();
private int adjustmentPeriod;
public TellerManager(ExecutorService e,
CustomerLine customers, int adjustmentPeriod) {
exec = e;
this.customers = customers;
this.adjustmentPeriod = adjustmentPeriod;
// Start with a single teller:
Teller teller = new Teller(customers);
exec.execute(teller);
workingTellers.add(teller);
}
public void adjustTellerNumber() {
// This is actually a control system. By adjusting
// the numbers, you can reveal stability issues in
// the control mechanism.
// If line is too long, add another teller:
if(customers.size() / workingTellers.size() > 2) {
// If tellers are on break or doing
// another job, bring one back:
if(tellersDoingOtherThings.size() > 0) {
2016-01-25 18:05:55 -08:00
Teller teller =
tellersDoingOtherThings.remove();
2015-06-15 17:47:35 -07:00
teller.serveCustomerLine();
workingTellers.offer(teller);
return;
}
// Else create (hire) a new teller
Teller teller = new Teller(customers);
exec.execute(teller);
workingTellers.add(teller);
return;
}
// If line is short enough, remove a teller:
if(workingTellers.size() > 1 &&
customers.size() / workingTellers.size() < 2)
reassignOneTeller();
// If there is no line, we only need one teller:
if(customers.size() == 0)
while(workingTellers.size() > 1)
reassignOneTeller();
}
// Give a teller a different job or a break:
private void reassignOneTeller() {
Teller teller = workingTellers.poll();
teller.doSomethingElse();
tellersDoingOtherThings.offer(teller);
}
@Override
public void run() {
try {
while(!Thread.interrupted()) {
TimeUnit.MILLISECONDS.sleep(adjustmentPeriod);
adjustTellerNumber();
System.out.print(customers + " { ");
for(Teller teller : workingTellers)
System.out.print(teller.shortString() + " ");
System.out.println("}");
}
} catch(InterruptedException e) {
System.out.println(this + "interrupted");
}
System.out.println(this + "terminating");
}
@Override
public String toString() { return "TellerManager "; }
}
public class BankTellerSimulation {
static final int MAX_LINE_SIZE = 50;
static final int ADJUSTMENT_PERIOD = 1000;
2016-01-25 18:05:55 -08:00
public static void
main(String[] args) throws Exception {
ExecutorService es = Executors.newCachedThreadPool();
2015-06-15 17:47:35 -07:00
// If line is too long, customers will leave:
CustomerLine customers =
new CustomerLine(MAX_LINE_SIZE);
2016-01-25 18:05:55 -08:00
es.execute(new CustomerSupplier(customers));
2015-06-15 17:47:35 -07:00
// Manager will add and remove tellers as necessary:
2016-01-25 18:05:55 -08:00
es.execute(new TellerManager(
es, customers, ADJUSTMENT_PERIOD));
2015-06-15 17:47:35 -07:00
if(args.length > 0) // Optional argument
TimeUnit.SECONDS.sleep(new Integer(args[0]));
else {
System.out.println("Press 'Enter' to quit");
System.in.read();
}
2016-01-25 18:05:55 -08:00
es.shutdownNow();
2015-06-15 17:47:35 -07:00
}
2015-09-07 11:44:36 -06:00
}
/* Output:
2016-07-22 14:45:35 -06:00
[768][193][807][125] { T1 T0 }
[125][634][682][267][954][506][639][213] { T2 T0 T1 }
[213][592][770][919][552][727][998][902] { T2 T0 T1 }
[552][727][998][902][769][373][313][683][177][526] { T3 T2
T1 T0 }
2015-06-15 17:47:35 -07:00
TellerManager interrupted
2016-07-22 14:45:35 -06:00
Teller 3 interrupted
CustomerSupplier interrupted
Teller 3 terminating
2016-07-27 11:12:11 -06:00
Teller 2 interrupted
Teller 2 terminating
2016-07-22 14:45:35 -06:00
TellerManager terminating
2016-07-27 11:12:11 -06:00
Teller 1 interrupted
Teller 0 interrupted
Teller 0 terminating
Teller 1 terminating
CustomerSupplier terminating
2015-09-07 11:44:36 -06:00
*/