Search code examples
javamultithreadingnullpointerexceptionruntime-errorexecutorservice

NullPointerException when Executing ExecutorService


I have to complete an Assignment that is a Producer/Consumer Program. I need one thread to read a file, one thread to reverse every other word starting with the second word, and one thread to write the words to a file. The rules are as follows:

  1. A word is any sequence of characters followed by a whitespace. Punctuation must stay at the end of the word even if reversed
  2. Your program must use three threads that communicate using blocking queues of size 2
  3. Other than the queues, the threads cannot communicate with one another and do not have references to one another
  4. Must obtain a input and output file from a JFileChooser object. Input needs to read a word at a time and pass it to reversing thread through a blocking queue. A processing thread needs to take every other word from the file and reverse it. All threads passed to output through blocking queue.
  5. Input thread needs to close file when finished reading
  6. Output needs to close file after finished writing words to it
  7. The thread that reverses words will terminate when there are no more words to reverse
  8. All Threads must terminate by either running out of work or being interrupted. No using System.exit.

I've completed the program but I keep receiving an NullPointerException coming from my WordReverser class and my WordWriter class.


I will list my code below.

It will follow the order

  1. Main class
  2. WordReader: Reads input from a file
  3. WordReverser: Reverses every other word starting with the second word
  4. WordWriter: Writes a word at a time to an output file

Main Class:

package ProducerConsumerAssignment;

import java.io.File;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;

import javax.swing.JFileChooser;

/**
 * Test the producer consumer package
 *
 * @author DanSchneider
 */
public class Tester {

    public static void main(String[] args) {
        final int MAX_SIZE = 2;
        File input, output;
        JFileChooser chooser = new JFileChooser();
        BlockingQueue fromReader = new ArrayBlockingQueue(MAX_SIZE);
        BlockingQueue toWriter = new ArrayBlockingQueue(MAX_SIZE);
        ExecutorService service = Executors.newCachedThreadPool();
        int returnVal, exitVal;

        do {
            input = output = null;
            returnVal = chooser.showOpenDialog(null);
            if (returnVal == JFileChooser.APPROVE_OPTION) {
                input = chooser.getSelectedFile();
            }
        } while (returnVal != JFileChooser.APPROVE_OPTION);

        do {
            exitVal = chooser.showSaveDialog(null);
            if (exitVal == JFileChooser.APPROVE_OPTION) {
                output = chooser.getSelectedFile();
            }
        } while (exitVal != JFileChooser.APPROVE_OPTION);

        Runnable reader = new WordReader(input, fromReader);
        Runnable rev = new WordReverser(fromReader, toWriter);
        Runnable writer = new WordWriter(output, toWriter);

        service.execute(reader);
        service.execute(rev);
        service.execute(writer);
        service.shutdown();
    }
}

ReaderClass:

package ProducerConsumerAssignment;

import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader;

import java.util.concurrent.BlockingQueue; import java.util.Scanner;

/**  * Reads words from a file and places them into a blocking queue to be read  *  * @author DanSchneider  */ public class WordReader implements Runnable {

    //The blocking queue to store
    private static BlockingQueue<CharSequence> bin;
    private final File loc;             //File to read from

    /**
     * Constructor for WordReader
     *
     * @param input the text file to read from
     * @param bin the blocking queue to store the words
     */
    public WordReader(final File input, BlockingQueue bin) {
        loc = input;
        WordReader.bin = bin;
    }

    /**
     * Called when being executed Reads words from a file and places into a
     * blocking queue
     */
    @Override
    public void run() {

        try (Scanner in = new Scanner(new FileReader(loc))) {
            while (in.hasNext()) {
                bin.offer(in.next());
            }
        } catch (FileNotFoundException ex) {
            System.err.printf("Error finding File!%n%s%n", ex);
        }
    } }

ReverserClass:

package ProducerConsumerAssignment;

import java.util.concurrent.BlockingQueue;

/**
 * Takes a word from a blocking queue and reverses it. Puts the reversed word
 * into another blocking queue.
 *
 * @author DanSchneider
 */
public class WordReverser implements Runnable {

    private static BlockingQueue<CharSequence> intake, store;
    private static int oddWord;

    /**
     * Constructor for Word Reverser
     *
     * @param intake the blocking queue to retrieve words from
     * @param store the blocking queue to store the words
     */
    public WordReverser(BlockingQueue intake, BlockingQueue store) {
        WordReverser.intake = intake;
        WordReverser.store = store;
        oddWord = 0;
    }

    /**
     * Called when being executed. Reverses a word by taking from intake and
     * places the reversed word into store
     */
    @Override
    public void run() {
        StringBuilder str = new StringBuilder(intake.poll());
        if (oddWord % 2 == 1) {
            str = reverseWord(str);
        }

        store.offer(str);
        ++oddWord;
    }

    /**
     * Reverses a word, leaving behind punctuation if there is any
     *
     * @param word the word to reverse
     * @return a stringbuilder object containing the reversed word
     */
    private StringBuilder reverseWord(StringBuilder word) {
        char punct = Character.MAX_VALUE;

        //If has punctuation at the end, remove the punctuation
        if (!Character.isLetterOrDigit(word.charAt(word.length() - 1))) {
            punct = word.charAt(word.length() - 1);
            word.deleteCharAt(word.length() - 1);
        }

        word = word.reverse();

        if (punct == Character.MAX_VALUE) {
            return word;
        }

        return word.append(punct);
    }
}

Writer Class:

package ProducerConsumerAssignment;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

import java.util.concurrent.BlockingQueue;

/**
 *
 * @author DanSchneider
 */
public class WordWriter implements Runnable {

    private static BlockingQueue<CharSequence> in;
    private final File output;

    /**
     * Constructs a WordWriter object
     *
     * @param file the file to write words to
     * @param queue the blocking queue to retrieve words from
     */
    public WordWriter(final File file, BlockingQueue queue) {
        output = file;
        in = queue;
    }

    /**
     * Executes when being called in a thread
     */
    @Override
    public void run() {
        try (BufferedWriter out = new BufferedWriter(new FileWriter(output))) {
            out.write(in.poll().toString() + " ");
        } catch (IOException ex) {
            System.err.printf("Error closing the file!%n%s%n", ex);
        }
    }
}

I believe the problem to be coming from my BlockingQueues but I am unsure. We were taught if there is not any words in the BlockingQueue then the thread will block and wait for there to be a word. But it seems as though it does not wait at all. Any help would be appreciated.

EDIT: The spot which throws the nullpointerexceptions are line 34 of WordReverser for the code: StringBuilder str = new StringBuilder(intake.poll());

and line 36 of the WordWriter class for the code:

out.write(in.poll().toString() + " ");

This is why I am confused. We were taught that a thread would block itself when trying to pull data from the BlockingQueue that was not there.


Solution

  • Well, I see at least 1 problem. You are using .poll() on the BlockingQueue, which the documentation says:

    Retrieves and removes the head of this queue, or returns {@code null} if this queue is empty.

    So, if your queue doesn't have any items and you poll from it and there is nothing there yet, your code will execute the rest of your on a null item.

    If you want to block until an item is available, you should use take():

    Retrieves and removes the head of this queue, waiting if necessary until an element becomes available.

    This will cause the thread to, like the documentation says, wait until an item is available. This operation also throws an InterruptedException because the calling thread can be interrupted. There are ways to stop the publisher/consumer in other (such as the poison pill), but I'm not going to get into them here.

    There are different ways to approach this, but I think you need to answer the following questions and take a look at your design:

    1. When is your WordReader done publishing data?
    2. How does WordReverser know it is out of data?
    3. How does WordWriter know there is no more data to write?
    4. How are you going to feed data between threads?
    5. When should your ExecutorService starter shutdown?

    You should also take a look at your static class members and try to ask yourself why you are using them.