0

I am trying to build a program that attempts to decrypt a file that has been encrypted using AES encryption for a school project. I have a list of ~100,000 english words, and want to implement multithreading within the program to optimise the time taken to attempt decryption with every word in the file.

I am having a problem when trying to stop the rest of the dictionary being searched in the event of the decryption completing successfully - "Attempting shutdown" is being printed to the console, but it appears that the threads continue working through the rest of the dictionary before the executor stops allocating new threads.

In my main program, the threads are run using this method:

private void startThreads(){
    ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    System.out.println("Maximum threads inside pool " + executor.getMaximumPoolSize());
    for (int i = 0; i < dict.size(); i++) {
        String word = dict.get(i);
        Grafter grafter = new Grafter("Grafter " + i,word);
        grafter.registerWorkerListener(thread -> {
            List results = thread.getResults();
            for (Iterator iter = results.iterator(); iter.hasNext();) {
                found = (boolean) iter.next();
                if(found){
                    System.out.println("THE WORD HAS BEEN FOUND!! Attempting shutdown");
                    executor.shutdown();
                }
            }
        });

        // Start the worker thread
        Thread thread = new Thread(grafter);
        thread.start();

    }
    if(!executor.isShutdown()) {
        executor.shutdown();
    }
}

And the implementation of the 'grafter' runnable class is as follows:

public class Grafter implements Runnable{

private String NAME;
private final String WORD;
private List listeners = new ArrayList();
private List results;

public Grafter(String name, String word){
    NAME = name;
    WORD = word;
}

public String getName(){
    return NAME;
}

@Override
public void run() {
    if (tryToDecrypt(WORD) == true){
        System.out.println("Thread: '" + NAME + "' successfully decrypted using word: \"" + WORD + "\".");
        results = new ArrayList();
        results.add(true);

        // Work done, notify listeners
        notifyListeners();
    }else{
        results = new ArrayList();
        results.add(false);

        // Work done, notify listeners
        notifyListeners();
    }
}

private void notifyListeners() {
    for (Iterator iter = listeners.iterator(); iter.hasNext();) {
        GrafterListener listener = (GrafterListener) iter.next();
        listener.workDone(this);
    }
}

public void registerWorkerListener(GrafterListener listener) {
    listeners.add(listener);
}

public List getResults() {
    return results;
}

private boolean tryToDecrypt(String word){
    //Decryption performed, returning true if successfully decrypted,
    //Returns false if not
}

}

The correct word is right at the top of the dictionary, so success is found early in the program's execution. However, there is a long pause (as the rest of the dictionary is worked through) before the program finishes.

I am looking for help on the positioning of executor.shutdown(), and how to stop the remainder of the dictionary being parsed after the decryption successfully completes.

Andy Shearer
  • 177
  • 2
  • 13
  • I did check through previous posts before posting this, however I was still getting the same problem after using the suggestion of `executor.shutdownNow(); executor.awaitTermination();` on that thread – Andy Shearer Feb 12 '17 at 10:01
  • 2
    Read the part about handling interruptions. – shmosel Feb 12 '17 at 10:01
  • [Shmosel](https://stackoverflow.com/users/1553851/shmosel) is exactly right. You need to [gracefully handle interrupts](https://docs.oracle.com/javase/tutorial/essential/concurrency/interrupt.html), and call `shutdownNow`. – Boris the Spider Feb 12 '17 at 10:05
  • Thanks I will look into implementing interrupts as mentioned in the other post. I have marked the post as a duplicate, thanks for your help. – Andy Shearer Feb 12 '17 at 10:13
  • 1
    @shmosel see [the answer](http://stackoverflow.com/a/42186277/2071828) - the interrupt is not the answer. The linked question is useful, but the problem is that the OP isn't using the `ExecutorService` in the first place. – Boris the Spider Feb 12 '17 at 10:13

1 Answers1

1

Your main problem is that you're not actually submitting your runnables to the executor. Thus calling shutdown on the executor has no effect on all the threads you've spawned.

Instead of creating a new thread instead do something like:

executor.submit(grafter)

This should get you most of the way, but if you want the service to shut down promptly and cleanly there's a bit more you can do. The link provided in the comments by shmosel should help you with that.

As an aside, the way you're doing this isn't going to be very efficient I don't think. Essentially you're creating a new task for every word in your dictionary which means that you have a large number of tasks (100K in your case). This means that the overhead of managing and scheduling all those task is likely to be a significant portion of the work performed by your program. Instead you may want to break the list of words up into some number of sublists each containing an equal number of words and then make your runnable process that sublist only.

d80tb7
  • 863
  • 2
  • 9
  • 20
  • Ha, didn't even notice that. Good spot! – Boris the Spider Feb 12 '17 at 10:08
  • Your last paragraph isn't exactly right as the OP is using a `FixedThreadPool` - this means that there should be the correct number of threads. Your're right that it's not ideal to spam the queue with loads of tasks, but there shouldn't be thrashing as there isn't an overcommit of threads. – Boris the Spider Feb 12 '17 at 10:10
  • Sure- the situation would be worse if he'd used some unbounded thread pool- but the fact remains that they're going to have to queue up a large number of tasks and then the thread pool will have to dequeue and parcel out all those tasks to the executors. I guess whether that's important depends on how much work get's done in tryToDecrypt but I'd be very surprised if a task per word was the most efficient way to do this, – d80tb7 Feb 12 '17 at 10:18