Search code examples
javamultithreadingsemaphorethread-synchronization

Using Semaphores in Java


I feel guilty reaching out to StackOverflow for help in school, but I've exhausted my resources and cannot figure this one out for the life of me. For one of my classes I am required to understand how to construct and properly use Semaphores in Java. One of the exercises has the following code:

import java.lang.Thread;
import java.util.concurrent.*;

public class ThreadSync
{
    private static boolean runFlag = true;

    public static void main( String[] args ) {
        Runnable[] tasks = new Runnable[37];
        Thread[] threads = new Thread[37];
        // create 10 digit threads
        for (int d=0; d<10; d++) {
            tasks[d] = new PrintDigit(d);
            threads[d] = new Thread( tasks[d] );
            threads[d].start();
        }
        // create 26 letter threads
        for (int d=0; d<26; d++) {
            tasks[d+10] = new PrintLetter((char)('A'+d));
            threads[d+10] = new Thread( tasks[d+10] );
            threads[d+10].start();
        }
        // create a coordinator thread
        tasks[36] = new PrintSlashes();
        threads[36] = new Thread( tasks[36] );
        threads[36].start();

        // Let the threads to run for a period of time
        try {
            Thread.sleep(50);
        }
        catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        runFlag = false;

        // Interrupt the threads
        for (int i=0; i<37; i++) threads[i].interrupt();
    }

    public static class PrintDigit implements Runnable 
    {
        int digit;
        public PrintDigit(int d) { digit=d; }
        public void run(){
            while (runFlag) {
                System.out.printf( "%d\n", digit);
            }
        }
    }
    public static class PrintLetter implements Runnable 
    {
        char letter;
        public PrintLetter(char c) { letter=c; }
        public void run(){
            while (runFlag) {
                System.out.printf( "%c\n", letter);
            }
         }
    }
    public static class PrintSlashes implements Runnable 
    {
        public void run(){
            while (runFlag) {
                System.out.printf( "%c\n", '/');
                System.out.printf( "%c\n", '\\');
            }
        }
    }
}

I need to modify the code, only by adding "semaphore-related statements" such that the program repeatedly prints out a '/' followed by three digits, then a '\' followed by two letters.

The example they give is as follows:

  /156\BA/376\YZ/654\JK/257\HG/445\DD…

Any help would be greatly appreciated. I'm usually pretty good at learning on my own but these threads have my head spinning! Thanks!


Solution

  • I question this instructor's teaching methods and coding practices, but I will answer the question. Actually the fact that I think the question is unduly complex makes me more willing to answer it, rather than have you figure it out for yourself.

    The example is somewhat counterintuitive because the threads are not being used for their normal purpose, which is to permit concurrent execution, but rather just as an exercise to understand semaphores. As a result, the semaphores will also have to be used in a somewhat nonstandard way, as signals between threads and not for their normal use of managing resources. You will understand how semaphores work in this contrived case, but may not end up understanding how they are used in a normal case. However, that can be fixed by reading the Javadoc on the Semaphore class, so back to your instructor's contrived case.

    It's pretty clear that the thread running PrintSlashes.run() is intended to act as a manager, determining when digit threads are run and when character threads are run. It needs to tell the digit threads when they can run, and it needs to tell the character threads when they can run. In addition, it needs to know when three digits have been printed, and it needs to know when two characters have been printed. That's four pieces of information that need to be transferred, for which the simplest model is to use four Semaphore objects.

    The Semaphore objects should represent the following four things:

    • Digits available to be printed
    • Digits that have been printed (after the most recent forward slash)
    • Characters available to be printed
    • Characters that have been printed (after the most recent backslash)

    PrintDigit.run() should acquire a permit from the digits available semaphore before printing each digit; that permits the digits available semaphore to limit digit printing to three at a time. After printing the digit, the method should release a permit from the digits printed semaphore - note, not the digits available semaphore - to indicate that a digit has been printed. I'm sure you can figure out what PrintLetter.run() should do. Incidentally, the fact that the thread is acquiring one semaphore but releasing a different semaphore is one of the ways in which this example is contrived; normally threads release the same semaphores they acquire.

    PrintSlashes.run() should release three permits from the digits available semaphore after printing a slash, then acquire three permits from the digits printed semaphore before printing a backslash. Releasing three digits available allows the PrintDigit threads to print the three digits, and waiting to acquire three digits printed ensures that three digits are printed before proceeding. Again, you should be able to figure out what happens after the backslash is printed.

    Note that the digit Semaphore objects should be initialized with 0 permits, so that the digit threads will wait for the slashes thread to kick things off.

    Two additional caveats:

    1. To make the code work as the sample output shows, you will also need to remove the \n from each printed string, or each character will be on a different line. However, the instructor may want each character on a different line, and have given you bad sample output. You'll have to guess which he really wants.

    2. If you want to make your code bulletproof, you may need to synchronize on System.out as discussed in:

      Synchronization and System.out.println

      However, your instructor probably doesn't care about that issue for this exercise.

    Finally, the following corrections should be made to fix additional bad practices in the code:

    • The wildcard import should not be used, since you're not using a lot of classes from the concurrent package. In my opinion, the wildcard import should never be used. Wildcard imports can impair the legibility of code by making it difficult to see where classes are coming from, and legibility is the single most important aspect of code.

    • Meaningful constants, such as "10", "26", "36" and "37" in this code, should not be written as literals, but instead should use defined constants, for example static final int NUMBER_OF_DIGITS = 10;. Then the code itself can use the symbol NUMBER_OF_DIGITS, making it more legible and also more maintainable, as you can easily change the value of the constant - for example to 8 if you want to convert the code to octal - without the worry that you'll miss some occurrences of the constant.

    • Meaningful constants should especially not be written as literals when they have a logical relationship. In this case, even static final int NUMBER_OF_CHARACTERS = 36 is not good practice; it should be static final int NUMBER_OF_CHARACTERS = NUMBER_OF_DIGITS + NUMBER_OF_LETTERS;, making the logical and numerical relationship clear.

    Whether you actually want to make these corrections in the work you turn in depends on what you think the instructor's reactions will be to being given good corrections.