CompSci 141 / CSE 141 / Informatics 101 Spring 2013
Project #5

Due date and time: Friday, June 7, 11:59pm


Introduction

Until now, most of you will have only written Java programs that perform one task at a time. You have likely become quite accustomed to the assumption that your program will execute sequentially, in the order you specify, with one method executing at a time, as well as the comfortable assumption that a simple chunk of code like this will always execute from beginning to end without interruption or interference.

    int balance = getBalance();
    balance += amount;
    setBalance(balance);

Assuming that getBalance( ) and setBalance( ) are implemented as they should be, you can feel comfortable in a single-threaded program that nothing can really go wrong here; the old balance will be fetched and placed into the variable balance, an amount will be added to it, and the result will become the new balance. (There is one possible problem, which is the fact that ints have a maximum value and the balance will roll back to a negative number if it passes that maximum.) This chunk of code is simple, straightforward, and predictable.

We call Java programs that perform only one task at a time single-threaded. However, not all Java programs fall into this category. In Java, a thread consists of a run-time stack and a program counter (pointing to the current bytecode instruction that is to be executed). Java programs can have multiple, simultaneously-executing threads, meaning that they can have more than one run-time stack (one per thread) and more than one program counter (again, one per thread), though all the threads will share the same static memory and the same heap. (There is essentially no theoretical limit on how many threads you can run at a time, though there are practical limitations, such as the reality that run-time stacks are pre-allocated to a size that can be as large as a megabyte or two each.) The practical effect of this is that a program can carry on doing multiple, separate tasks simultaneously, with this simultaneity achieved by literally running the threads on separate processors, if you have at least as many processors as threads, or by timeslicing — rapidly switching between them, so that one processor can contribute to the work of more than one thread — if you have more threads than processors.

When first confronted with this notion, a reasonable question to ask is "Why would I want my program to be able to do more than one thing at a time?" There are a variety of reasons. A few of these reasons are:

Concurrency, which is achieved in Java by using threads (and is achieved with a variety of mechanisms in other programming languages), has emerged as a central theme in programming language design and software engineering in recent years. This project begins your exploration of the topic of concurrency by asking you to write a multi-threaded Java program. In particular, we'll be exploring some of the concepts underlying shared-state concurrency, where threads share objects as a way of communicating with one another. Our exploration will be done within the context of a simulation of a very rudimentary operating system.


BooOS: Our operating system

One of the primary jobs of an operating system is to manage access to various resources. On a typical personal computer, these resources include the display, input devices such as the keyboard and mouse, memory, disk drives and other external storage, speakers, and network adapters. This access must be managed even in the face of concurrent attempts by multiple programs to use these resources. If one program is writing to a file, other programs may be prohibited from also writing to that same file; if more than one program is attempting to play a sound through the speakers, the sounds may be mixed and played together.

Our operating system for this assignment is called BooOS. BooOS doesn't manage all of the resources of a personal computer; it's little. Instead, it focuses on three things:

BooOS can run any number of programs simultaneously, each of which is a sequence of operations that are either file creations, file reads, file writes, line prints (printing one line of output to a printer), or calculations. Only one program is permitted to print to a printer at a time; similarly, no two programs can access the same file at the same time. File and printer operations are not instantaneous; they take time, and BooOS has the job of making sure that programs that attempt to access a file are suspended while another program is already accessing it.

There are no fairness considerations in BooOS. Suppose Program A begins accessing the file X. While its access is proceeding, Program B attempts an access to the same file and is blocked; just after that, Program C also attempts an access to the same file and is blocked. When Program A's access completes, either Program B or Program C can be chosen to gain access to the file next. The same rule applies to printers.


The program: a BooOS simulator

In this project, you're being asked to write a BooOS simulator, which is capable of executing multiple simultaneous programs that consist of sequences of five operations: file creations, file reads, file writes, line prints, and sleeps (periods where the program is inactive). The sleeps simulate the calculations that a program might be performing in between file and printer accesses; for our simulator, though, we won't be concerned with what those calculations are or what values they produce.

Initially, the simulator reads an input file that consists of a set of programs to execute. All of the programs should begin immediately after the simulator finishes reading the last one.

For your simulator, assume that BooOS is running on a hardware platform that has the following characteristics. (These characteristics are slow, relative to realistic computer hardware, but serve to slow our simulation down to the point where we can see what it's doing along the way.)

Your simulator will store files in memory, rather than on disk. Printer output will be written to a text file, rather than printed to an actual printer.

The simulator is not required to print output to the console, but you might find it useful to print output whenever anything interesting happens (e.g., a program starts, a program ends, a program begins a read operation, a program finishes a read operation, and so on). Be aware that the System.out.println( ) method provides synchronized access to the console automatically; each call to println( ) blocks other threads from printing to the console until it's done. However, if you want to print a line of output to the console by using multiple calls to System.out.print, you may find that output from one thread will be interleaved with output from another; for this reason, it is better to build the entire line of output as a string, then print that string using one single call to System.out.println.


Input file format

An example of an input file for your simulator follows. The italicized portions of the example below are not considered part of the input file; they're included here to explain the file's contents.

2                       The number of programs
FirstProgram            The name of the first program
CREATE File1 100        Create a file called File1 of size 100 bytes
WRITE File1 0 C         Write the character 'C' to byte 0 of the file File1
READ File1 10           Read from position 10 in File1
SLEEP 200               Sleep for 200 milliseconds
WRITE File1 10 F        Write the character 'F' to byte 10 of the file File1
PRINT File1             Print the contents of File1 to the printer on one line
END                     End of FirstProgram
Program2                The name of the second program
CREATE File2 10         Create a file called File2 of size 10 bytes
WRITE File2 0 X         Write the character 'X' to byte 0 of the file File2
SLEEP 300               Sleep for 300 milliseconds
WRITE File2 1 Y         Write the character 'Y' to byte 1 of the file File2
PRINT File2             Print the contents of File2 to the printer on one line
SLEEP 500               Sleep for 500 milliseconds
PRINT File1             Print the contents of File1 to the printer on one line
END                     End of SecondProgram

In general, the format of the input file is as follows.

You may assume that the input file is properly formatted according to these rules, though, of course, you may not assume that the input files we'll be testing the program with will look exactly like the example file shown above.


More precise semantics of commands

The semantics of each command follow.

(You may be wondering why the READ command doesn't seem to have any real use, since there are no commands that take the character that was read and do anything with it. Remember, though, that the intent here is not to provide a programming language; it's just to simulate the interaction of multiple programs wanting to perform input and output to the same devices simultaneously. In such a context, it's less important what the operations are and more important when they occur relative to one another and how simultaneous operations affect one another.)


Running your simulator

Your main( ) method should be written in a class called Simulator; don't put the class into a package. It should accept two command-line parameters: the name of the input file containing the programs it should execute, and the name of the output file to which printer output should be written. We should be able to run your program from the command line using the following command:

    java Simulator input1.txt output1.txt

...and, of course, should be free to replace input1.txt with the name of any (properly formatted) input file we'd like to test your simulator with, and output1.txt with the name of any output file we'd like.


Design requirements

This is an exploration of concurrency, so you'll need to use multiple threads in your solution. Furthermore, this is an exploration of shared-state concurrency, so you'll also need to have shared objects. I'm requiring the following basic design for your simulator:


Deliverables

You need to submit all of your Java source files (.java), including any that were provided to you. Do not submit compiled versions of your program, or other files generated by your development environment.

Follow this link for a discussion of how to submit your project. Remember that we do not accept paper submissions of your projects, nor do we accept them via email under any circumstances.