Running configurations with a limit
Sorry vesii if I don't fully understand or even misunderstand your problem, but your English is not very good and I have problems even after your comments to see what the problem is in using multiple threads.
Anyway, I suggest you make your ConfigStruct
class implement the Runnable
interface, which is easy because it already has a run()
method. You only need to get rid of throwing a checked exception, so I further suggest to make ConfigurationException
a RuntimeException
which you don't have to declare in the method signature.
Unfortunately you have not provided a full MCVE, only code snippets. So I have to make up the rest in order to be able to compile and run your code. I just added a few simple helper/dummy classes. My solution looks like this:
package de.scrum_master.app;
public enum GalishFlags {
RUN
}
package de.scrum_master.app;
public class ConfigurationException extends RuntimeException {
private static final long serialVersionUID = 1L;
public ConfigurationException(String message, Throwable cause) {
super(message, cause);
}
}
package de.scrum_master.app;
import java.io.IOException;
public class ExternalCommandExecutor {
public static String execute(final String cmd, final String error, final boolean runInBackground, final boolean retry) throws IOException {
System.out.println("Executing external command: " + cmd);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
return cmd;
}
}
As you can see, the command executor waits for 100 ms after printing something onto the console. You can also change that to 1000 ms if you want the program to run slower or even randomise it in order to emulate commands taking different times to finish.
Now we need a little driver application in which we generate configurations and run them. The key for solving your problem of never running more than 5 threads at the same time is to create a fixed thread pool via Executors.newFixedThreadPool(5)
. The rest should be easy to understand.
package de.scrum_master.app;
import java.io.IOException;
public class ConfigStruct implements Runnable {
private String name;
public ConfigStruct(String name) {
this.name = name;
}
@Override
public void run() {
StringBuffer runCmd = generateGalishFullCommand(GalishFlags.RUN);
try {
ExternalCommandExecutor.execute(runCmd.toString(), "Failed to run " + name, true, true);
} catch (IOException e) {
throw new ConfigurationException(e.getMessage(), e);
}
}
private StringBuffer generateGalishFullCommand(GalishFlags run) {
return new StringBuffer("Galish full command for ConfigStruct '" + name + "'");
}
}
package de.scrum_master.app;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class Application {
public void runConfigurations(List<ConfigStruct> configurations) {
for (ConfigStruct configuration : configurations) {
try {
configuration.run();
} catch (ConfigurationException e) {
continue;
}
}
}
public void runConfigurationsThreaded(List<ConfigStruct> configurations) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (ConfigStruct configuration : configurations)
executorService.execute(configuration);
executorService.shutdown();
try {
executorService.awaitTermination(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
List<ConfigStruct> configurations = new ArrayList<>();
for (int i = 1; i <= 13; i++)
configurations.add(new ConfigStruct("Configuration " + i));
long startTime = System.currentTimeMillis();
new Application().runConfigurations(configurations);
System.out.println("Total time (1 thread) = " + (System.currentTimeMillis() - startTime) + " ms");
System.out.println();
startTime = System.currentTimeMillis();
new Application().runConfigurationsThreaded(configurations);
System.out.println("Total time (5 threads) = " + (System.currentTimeMillis() - startTime) + " ms");
}
}
The console log will look something like this:
Executing external command: Galish full command for ConfigStruct 'Configuration 1'
Executing external command: Galish full command for ConfigStruct 'Configuration 2'
Executing external command: Galish full command for ConfigStruct 'Configuration 3'
Executing external command: Galish full command for ConfigStruct 'Configuration 4'
Executing external command: Galish full command for ConfigStruct 'Configuration 5'
Executing external command: Galish full command for ConfigStruct 'Configuration 6'
Executing external command: Galish full command for ConfigStruct 'Configuration 7'
Executing external command: Galish full command for ConfigStruct 'Configuration 8'
Executing external command: Galish full command for ConfigStruct 'Configuration 9'
Executing external command: Galish full command for ConfigStruct 'Configuration 10'
Executing external command: Galish full command for ConfigStruct 'Configuration 11'
Executing external command: Galish full command for ConfigStruct 'Configuration 12'
Executing external command: Galish full command for ConfigStruct 'Configuration 13'
Total time (1 thread) = 1374 ms
Executing external command: Galish full command for ConfigStruct 'Configuration 1'
Executing external command: Galish full command for ConfigStruct 'Configuration 2'
Executing external command: Galish full command for ConfigStruct 'Configuration 3'
Executing external command: Galish full command for ConfigStruct 'Configuration 4'
Executing external command: Galish full command for ConfigStruct 'Configuration 5'
Executing external command: Galish full command for ConfigStruct 'Configuration 6'
Executing external command: Galish full command for ConfigStruct 'Configuration 7'
Executing external command: Galish full command for ConfigStruct 'Configuration 8'
Executing external command: Galish full command for ConfigStruct 'Configuration 10'
Executing external command: Galish full command for ConfigStruct 'Configuration 9'
Executing external command: Galish full command for ConfigStruct 'Configuration 11'
Executing external command: Galish full command for ConfigStruct 'Configuration 13'
Executing external command: Galish full command for ConfigStruct 'Configuration 12'
Total time (5 threads) = 344 ms
Please note:
- When running in a single-threaded loop, the runtime is > 1,300 ms (13 x 100 ms).
- When running in a with the thread pool of 5 threads, the runtime is > 300 ms (3 x 100 ms) - exactly what you would expect according to your requirement to process 5 configurations at the same time.
- Because of multi-threading the log output is not straight 1 to 13 but a little bit different, here 8, 10, 9, 11, 13, 12 at the end. For different processing times per thread it would look even more different.
Update: If you want to see some more variation, just add a random element in the threads' sleep times and extend the logging a bit:
package de.scrum_master.app;
import java.io.IOException;
import java.util.Random;
public class ExternalCommandExecutor {
private static final Random RANDOM = new Random();
public static String execute(final String cmd, final String error, final boolean runInBackground, final boolean retry) throws IOException {
long sleepTime = 100 + 100 * (RANDOM.nextInt(3));
System.out.println("Executing external command: " + cmd + ", sleeping for " + sleepTime + " ms");
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Finished execution: " + cmd);
return cmd;
}
}
Then the console log could look like this:
Executing external command: Galish full command for ConfigStruct 'Configuration 1', sleeping for 300 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 1'
Executing external command: Galish full command for ConfigStruct 'Configuration 2', sleeping for 100 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 2'
Executing external command: Galish full command for ConfigStruct 'Configuration 3', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 3'
Executing external command: Galish full command for ConfigStruct 'Configuration 4', sleeping for 300 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 4'
Executing external command: Galish full command for ConfigStruct 'Configuration 5', sleeping for 100 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 5'
Executing external command: Galish full command for ConfigStruct 'Configuration 6', sleeping for 100 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 6'
Executing external command: Galish full command for ConfigStruct 'Configuration 7', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 7'
Executing external command: Galish full command for ConfigStruct 'Configuration 8', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 8'
Executing external command: Galish full command for ConfigStruct 'Configuration 9', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 9'
Executing external command: Galish full command for ConfigStruct 'Configuration 10', sleeping for 100 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 10'
Executing external command: Galish full command for ConfigStruct 'Configuration 11', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 11'
Executing external command: Galish full command for ConfigStruct 'Configuration 12', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 12'
Executing external command: Galish full command for ConfigStruct 'Configuration 13', sleeping for 100 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 13'
Total time (1 thread) = 2314 ms
Executing external command: Galish full command for ConfigStruct 'Configuration 1', sleeping for 300 ms
Executing external command: Galish full command for ConfigStruct 'Configuration 2', sleeping for 300 ms
Executing external command: Galish full command for ConfigStruct 'Configuration 3', sleeping for 200 ms
Executing external command: Galish full command for ConfigStruct 'Configuration 5', sleeping for 300 ms
Executing external command: Galish full command for ConfigStruct 'Configuration 4', sleeping for 100 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 4'
Executing external command: Galish full command for ConfigStruct 'Configuration 6', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 3'
Executing external command: Galish full command for ConfigStruct 'Configuration 7', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 1'
Finished execution: Galish full command for ConfigStruct 'Configuration 2'
Executing external command: Galish full command for ConfigStruct 'Configuration 8', sleeping for 200 ms
Executing external command: Galish full command for ConfigStruct 'Configuration 9', sleeping for 100 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 5'
Executing external command: Galish full command for ConfigStruct 'Configuration 10', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 6'
Executing external command: Galish full command for ConfigStruct 'Configuration 11', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 9'
Finished execution: Galish full command for ConfigStruct 'Configuration 7'
Executing external command: Galish full command for ConfigStruct 'Configuration 12', sleeping for 200 ms
Executing external command: Galish full command for ConfigStruct 'Configuration 13', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 8'
Finished execution: Galish full command for ConfigStruct 'Configuration 10'
Finished execution: Galish full command for ConfigStruct 'Configuration 11'
Finished execution: Galish full command for ConfigStruct 'Configuration 13'
Finished execution: Galish full command for ConfigStruct 'Configuration 12'
Total time (5 threads) = 609 ms
See how in single-threaded mode still everything is FIFO (first in, first out)?
Please also note that if you count the number of active (unfinished) threads on the console, it is never more than 5, no matter the execution time. At the end the last 5 threads finish. And still the total execution time is clearly smaller than in the single-threaded case.
Update 2: Last but not least, if you increase the number of elements in the main loop from 13 to a bigger number, say 100, you will notice that in the end the total execution time of the multi-threaded solution is roughly 1/5 (or generally 1 divided by the number of threads in the fixed thread pool) of the single-threaded solution. This is because the threads don't do much else but wait and print to the console. If they actually do more such as heavy calculations or a lot of I/O then the improvement will be less dramatic but still significant.
My try with 100 configuration elements yielded the following output (abbreviated):
Executing external command: Galish full command for ConfigStruct 'Configuration 1', sleeping for 300 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 1'
(...)
Executing external command: Galish full command for ConfigStruct 'Configuration 100', sleeping for 300 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 100'
Total time (1 thread) = 20355 ms
Executing external command: Galish full command for ConfigStruct 'Configuration 2', sleeping for 100 ms
Executing external command: Galish full command for ConfigStruct 'Configuration 1', sleeping for 300 ms
(...)
Executing external command: Galish full command for ConfigStruct 'Configuration 100', sleeping for 200 ms
Finished execution: Galish full command for ConfigStruct 'Configuration 99'
Finished execution: Galish full command for ConfigStruct 'Configuration 93'
Finished execution: Galish full command for ConfigStruct 'Configuration 94'
Finished execution: Galish full command for ConfigStruct 'Configuration 95'
Finished execution: Galish full command for ConfigStruct 'Configuration 100'
Total time (5 threads) = 3923 ms
See? ~20 seconds / 5 = ~4 seconds