4

I have to make a program that searches through a bunch of lines from a file and tries to find a given substring. If it finds it, it prints out the line. Each line that I read is created as a thread and each thread searches a single line of the file. Thats not a problem so far. What I need the program to do is print the end results (the line of text) in the same order that the threads were created. I.E. thread 6 shouldnt be printing before thread 2. Its fine that the threads run at the same time, the printing order just needs to be maintained. I can not use the join method as I don't want the next one to wait for the other to finish entirely before starting, I do want them to be running at the same time. Any suggestions on doing this? Also, the file can have any number of lines so I cant hardcode the number of threads.

THE THREADS SHOULD DO THEIR OWN PRINTING. THE MAIN DOES NOT DO THE PRINTING.

Teddy Black
  • 193
  • 2
  • 14
  • 1
    "Join" does not mean "wait for the others to finish before starting", unless you intentionally program it that way... – Oliver Charlesworth Sep 20 '13 at 21:37
  • 1
    Creating an entire thread just to process a single line of a file is horribly inefficient and may crash your application if the file is too long. Dispatch the lines to a thread pool using one of the methods described in some of the answers. – reggert Sep 21 '13 at 00:45

4 Answers4

8

First of all the order of threaded applications is hard to define. See here: unwanted output in multithreading

If you want the output in a particular order then you probably should use an ExecutorService which will return a Future. You submit Callable<String> classes to the service each which return the results. The submit returns a Future<String>. You then can call get() from the Future in the same order that you submitted the jobs to the service.

// create a thread pool with 10 workers
ExecutorService threadPool = Executors.newFixedThreadPool(10);
// or you can create an open-ended thread pool
// ExecutorService threadPool = Executors.newCachedThreadPool();
// define your jobs somehow as a Callable that returns the line (prolly a string)
List<Future<String>> futures = threadPool.invokeAll(jobsToDo);
// once we have submitted all jobs to the thread pool, it should be shutdown
threadPool.shutdown();
// now we can go through the futures and get the results in order
for (Future<String> future : futures) {
    // this returns the results from the `call()` in order or it throws
    String resultFromCall = future.get();
}

You job Callable class would look something like:

public class MyCallable implements Callable<String> {
    private String input;
    public MyCallable(String input) {
        this.input = input;
    }
    public String call() {
        // search the input string
        String result = search(input);
        return result;
    }
}
Community
  • 1
  • 1
Gray
  • 115,027
  • 24
  • 293
  • 354
  • Yeah I always forget about that. Thanks @SamiKorhonen. – Gray Sep 20 '13 at 22:33
  • so I should implement Callable instead of Runnable? – Teddy Black Sep 22 '13 at 05:42
  • @TeddyBlack the only difference between Callable and Runnable (besides the throws signature) is that Callable can return a value and Runnable cannot. An Executor can and will hand you back the return value of a Callable, but if you don't need that feature, then Runnables will work in much the same way. An Executor can accept work via either. – Mike Clark Sep 22 '13 at 09:39
  • In this case @TeddyBlack, since you need to return a value from your processing of the line, then yes you should implement Callable. – Gray Sep 22 '13 at 14:40
  • Then who does the printing exactly? The thread itself should print, not send a line to main for main to print it. – Teddy Black Sep 22 '13 at 15:26
  • Main needs to print it to ensure the line order. If the thread printed it then it would either have to wait or be out of order. Neither of which u want. If you need a thread to to the printing then fork another one to consume the Future list. – Gray Sep 22 '13 at 20:30
2

Actually, you can use join(). But you do need to call it at the appropriate time and place for your requirements. Updated for your new requirement "Main thread should not print the results, the worker threads should print the results".

NOTE: This type of program is actually better written using the utility classes in java.util.concurrent. However, I wrote this example using the threading "base classes" to help with learning. Please note reggert's comment (above) -- blindly creating one thread per line of text in a file may result in so many threads that your program crashes or OS gets overwhelmed. This kind of work delegation is better done with a thread pool (e.g. java.util.concurrent.Executors).

public class MultiThreadedLineSearcher {

    public static void main(String[] args) throws Exception {
        Thread previousThread = null;
        for (int i = 0; i < LINES.length; i++) {
            JobRunnable job = new JobRunnable(i, LINES[i], previousThread);
            Thread thread = new Thread(job, "T-" + i);
            thread.start();
            previousThread = thread;
        }
        if (previousThread != null) {
            previousThread.join();
        }
        System.out.println("Program done.");
    }

    public static class JobRunnable implements Runnable {
        private final int _lineIdx;
        private final String _lineText;
        private final Thread _threadToWaitForBeforePrinting;

        public JobRunnable(int lineIdx, String lineText,
                Thread threadToWaitForBeforePrinting) {
            _lineIdx = lineIdx;
            _lineText = lineText;
            _threadToWaitForBeforePrinting = threadToWaitForBeforePrinting;
        }

        public void run() {
            try {
                boolean matched = FIND_ME.matcher(_lineText).find();
                String currentThreadName = Thread.currentThread().getName();
                System.out.println("Thread " + currentThreadName
                        + " is done with its work.");
                if (_threadToWaitForBeforePrinting != null) {
                    System.out.println("Thread " + currentThreadName
                            + " will wait for thread "
                            + _threadToWaitForBeforePrinting.getName()
                            + " before printing its results.");
                    _threadToWaitForBeforePrinting.join();
                }
                System.out.println("RESULT: " + _lineIdx + " matched? "
                        + matched + " (Printed on Thread "
                        + currentThreadName + ")");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    final static String[] LINES = new String[] {
        "Sed ut perspiciatis unde omnis iste natus error sit voluptatem",
        "accusantium doloremque laudantium, totam rem aperiam, eaque ipsa",
        "quae ab illo inventore veritatis et quasi architecto beatae vitae",
        "dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas",
        "sit aspernatur aut odit aut fugit, sed quia consequuntur magni",
        "dolores eos qui ratione voluptatem sequi nesciunt. Neque porro",
        "quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur,",
        "adipisci velit, sed quia non numquam eius modi tempora incidunt",
        "ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad",
        "minima veniam, quis nostrum exercitationem ullam corporis",
        "suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?",
        "Quis autem vel eum iure reprehenderit qui in ea voluptate velit",
        "esse quam nihil molestiae consequatur, vel illum qui dolorem eum",
        "fugiat quo voluptas nulla pariatur?" };

    // Match only words that are 11 characters or longer
    final static java.util.regex.Pattern FIND_ME = 
            java.util.regex.Pattern.compile("\\w{11,}");
}

And here's how to do it can be done with an Executor:

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

public class MultiThreadedLineSearcherExecutor {

    static final int MAX_THREADS = 10;

    public static void main(String[] args) throws Exception {
        // Create as many threads as there are lines of text,
        // but do not exceed 10 threads
        int lineCount = LINES.length;
        int threadPoolSize = Math.min(MAX_THREADS, lineCount);
        System.out.println("Number of lines = " + lineCount);
        System.out.println("Thread pool size = " + threadPoolSize);
        ExecutorService executor = Executors.newFixedThreadPool(threadPoolSize);
        Future previousFuture = null;
        for (int i = 0; i < lineCount; i++) {
            JobRunnable job = new JobRunnable(i, LINES[i], previousFuture);
            previousFuture = executor.submit(job);
        }
        executor.shutdown();
        executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
        System.out.println("Program done.");
    }

    public static class JobRunnable implements Runnable {
        private final int _lineIdx;
        private final String _lineText;
        private final Future _futureToWaitForBeforePrinting;

        public JobRunnable(int lineIdx, String lineText,
                Future previousFuture) {
            _lineIdx = lineIdx;
            _lineText = lineText;
            _futureToWaitForBeforePrinting = previousFuture;
        }

        public void run() {
            try {
                boolean matched = FIND_ME.matcher(_lineText).find();
                String currentThreadName = Thread.currentThread().getName();
                System.out.println("Thread " + currentThreadName
                        + " is done with its work on line " + _lineIdx);
                if (_futureToWaitForBeforePrinting != null) {
                    System.out.println("Thread " + currentThreadName
                            + " will wait for future "
                            + _futureToWaitForBeforePrinting
                            + " before printing its results.");
                    _futureToWaitForBeforePrinting.get();
                }
                System.out.println("RESULT: " + _lineIdx + " matched? "
                        + matched + " (Printed on Thread "
                        + currentThreadName + ")");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }
    }

    final static String[] LINES = new String[] {
        "Sed ut perspiciatis unde omnis iste natus error sit voluptatem",
        "accusantium doloremque laudantium, totam rem aperiam, eaque ipsa",
        "quae ab illo inventore veritatis et quasi architecto beatae vitae",
        "dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas",
        "sit aspernatur aut odit aut fugit, sed quia consequuntur magni",
        "dolores eos qui ratione voluptatem sequi nesciunt. Neque porro",
        "quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur,",
        "adipisci velit, sed quia non numquam eius modi tempora incidunt",
        "ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad",
        "minima veniam, quis nostrum exercitationem ullam corporis",
        "suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?",
        "Quis autem vel eum iure reprehenderit qui in ea voluptate velit",
        "esse quam nihil molestiae consequatur, vel illum qui dolorem eum",
        "fugiat quo voluptas nulla pariatur?" };

    // Match only words that are 11 characters or longer
    final static java.util.regex.Pattern FIND_ME = 
            java.util.regex.Pattern.compile("\\w{11,}");
}
Mike Clark
  • 10,027
  • 3
  • 40
  • 54
1

I can not use the join method as I don't want the next one to wait for the other to finish entirely before starting, I do want them to be running at the same time.

You could make them populate an array (where each one "knows" the element to populate to start with, as part of its initial state), then join on all the threads so that you wait for them all to finish, and then just print out the contents of the array in order.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
0

I would use some sort of a collection in order to sort the results. Each thread will start a Runnable that has a counter. Something like this:

private Map<Integer, String> map = new HashMap<Integer, String>();
int counter = 1;
while (line = getNextLine()) {
    Runnable runnable = new PrinterRunnable(line, counter);
    ++counter;
}

The Runnable would look something like:

public PrinterRunnable implements Runnable {

   private String line;
   private Integer counter;

   public PrinterRunnable(String line, Integer counter) {
       this.line = line;
       this.counter = counter;
   }


   public run () {
      // Do some work on line and keep in the map the result where counter is the key
   }
}

Important: In this example, PrinterRunnable should be an inner class of the class running the code in order for it to be able to access the map (or you should pass a reference to the map in some other way).

Finally you can sort the keySet() of the map and then print the values accordingly.

Avi
  • 21,182
  • 26
  • 82
  • 121