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
|
|
|
*/
|