简体   繁体   中英

Is there a more elegant way to launch a thread based on a list?

My program/class is getting a list of classes (eg C-1() through C-100()) that need to be run in parallel threads. Each one is its own Class and has its own executable so i don't need to compile, just run. While each class takes a parameter, the logic inside each can be very different. So no hope of launching one class with a parameter multiple times.

The list of classes is variable. There may be one class (C-3()) or multiple (C-1(),C-2(),C-4(),C-3()) and they may or may not be in any order.

I have used the bulk method with a loop and a switch statements but coding 100 of those seems unnecessarily complex and frankly just looks bad. But it works and worst case, will do the job. But it bothers me.

case ("C-1")
{
   new C-1("parm").start();
}
etc .... x 100

the lambda functions might get me there but its outside my experience.

I didnt want to shell it out. That seems both inefficient and potentially a performance killer.

In a perfect world, I would dynamically pull the item from the list and launch it. But I cant figure out how to replace the objectname dynamically. I dont want to slow it down with any clever linking. My expertise isnt enough to tackle that one.

It would also have been nice to add something so that if the list is less than 10, it would run it in the same thread and only go massively parallel if it was above that. But thats also outside my expertise.

The accepted best way to approach this problem is to use a ThreadPool . The idea is that you will spawn a known number of threads, and use those worker threads to work through a queue of tasks. The threads themselves can be reused, preventing the overhead of thread creation.

https://howtodoinjava.com/java/multi-threading/java-thread-pool-executor-example/

package com.howtodoinjava.threads;

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

public class ThreadPoolExample
{
    public static void main(String[] args)
    {
        ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);

        for (int i = 1; i <= 5; i++)
        {
            Task task = new Task("Task " + i);
            System.out.println("Created : " + task.getName());

            executor.execute(task);
        }
        executor.shutdown();
    }
}

In a perfect world, I would dynamically pull the item from the list and launch it. But I cant figure out how to replace the objectname dynamically.

The Java subsystem and technique for this kind of dynamic operation is called "reflection". The java.lang.Class class plays a central role here, with most of the rest of the key classes coming from package java.lang.reflect . Reflection permits you to obtain the Class object for a class you identify by name, to create instances of that class, and to invoke methods on those instances.

If your C-* classes all have a common superclass or interface that defines the start() method ( Thread ?) then you could even perform normal method invocation instead of reflective.

Provided that all the classes you want to dynamically instantiate provide constructors that accept the same parameter type and to which you want to pass the same argument value, you can use it to save writing 100-way conditionals, or a hundred different adapter classes, or similar, for your case. Schematically, it would work along these lines:

  • obtain or create a fully-qualified class name for the wanted class, let's say className .

  • obtain the corresponding Class

     Class<?> theClass = Class.forName(className); 
  • Obtain a Constructor representing the constructor you want to use. In your example, the constructor takes a single parameter of a type compatible with String . If the declared parameter type is in fact String itself (as opposed to Object or Serializable , or ...) then that would be done like so:

     Constructor<?> constructor = theClass.getConstructor(String.class); 
  • Having that in hand, you can instantiate the class:

     Object theInstance = constructor.newInstance("parm"); 

Your path from there depends on whether there is a common supertype, as mentioned above. If there is, then you can

  • Cast the instance and invoke the method on it normally:

     ((MySupertype) theInstance).start(); 

Otherwise, you'll need to invoke the method reflectively, too. This is somewhat simplified by the fact that the method of interest does not take any parameters:

  • Obtain a Method instance.

     Method startMethod = theClass.getMethod("start"); 
  • Invoke the method on your object

     startMethod.invoke(theInstance); 

You also mention,

It would also have been nice to add something so that if the list is less than 10, it would run it in the same thread and only go massively parallel if it was above that.

None of the above has anything directly to do with starting new threads in which to run your code. If that's something that the start() methods will do themselves (for example, if the classes involved have java.lang.Thread as a superclass) then the only alternative for avoiding each object running on its own thread is to use a different method.

On the other hand, if you're starting from everything running in one thread and looking to parallelize, then using a thread pool as described in @PaulProgrammer's answer is a great way to go. Note well that if the tasks are independent of each other, as seems the case from your description, then there's not much point in trying to ensure that they all run concurrently. More threads than you have cores to run them on does not really help you, and a thread pool is useful for queueing up tasks for parallel execution. Of course it would be simple to check the size() of your list to decide whether to send tasks to a thread pool or to just run them directly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM