Home > Software engineering >  How to start thread when at least one is finished
How to start thread when at least one is finished

Time:05-06

I have 4 threads. Each prints the given letter x times every x second. The task is to start 3 threads at once and the 4th one when at least one of the previous threads is finished. I don't know how to notify this last thread to run in due time.

CodePudding user response:

CompletableFutures are the modern way to accomplish this in an expressive way.

public static void main(String[] args) {
    ExecutorService executor = Executors.newFixedThreadPool(3);
    
    CompletableFuture<Void> a = CompletableFuture.runAsync(() -> print("A"), executor);
    CompletableFuture<Void> b = CompletableFuture.runAsync(() -> print("B"), executor);
    CompletableFuture<Void> c = CompletableFuture.runAsync(() -> print("C"), executor);
    CompletableFuture.anyOf(a, b, c).thenRunAsync(() -> print("D"), executor);
}

private static void print(String taskName) {
    for (int i = 0; i < 10_000;   i) {
        System.out.println("Task "   taskName   ": "   i);
    }
}

Run A, B and C, then when any one of them is done, run D.

You can simplify it even further by removing the executor and just using the fork/join pool, but it doesn't work so well in a self-contained example because they're daemon threads; the program will just end very quickly before it does much of anything.

CodePudding user response:

Depends what is your assignment? Do you care about the work? The threads?

You can always "hack it" by making thread pool with three threads and give it four tasks:

ExecutorService es = Executors.newFixedThreadPool(3);
    es.execute(() -> {
        for (int i = 0; i < 10; i  ) {
            System.out.println("A");
            try {Thread.sleep(100);} catch (InterruptedException e) {}
        }
    });
    es.execute(() -> {
        for (int i = 0; i < 10; i  ) {
            System.out.println("B");
            try {Thread.sleep(100);} catch (InterruptedException e) {}
        }
    });
    es.execute(() -> {
        for (int i = 0; i < 10; i  ) {
            System.out.println("C");
            try {Thread.sleep(100);} catch (InterruptedException e) {}
        }
    });
    es.execute(() -> {
        for (int i = 0; i < 10; i  ) {
            System.out.println("D");
            try {Thread.sleep(100);} catch (InterruptedException e) {}
        }
    });

Or alternatively make the threads yourself (D thread first), and at the end of the work of each (ABC) ask if D is running, if not, start it.

There is so many solutions to this with wildly different complexity, and whether they are alright depends solely on your context.

CodePudding user response:

To use semaphores to wait until one of 3 threads finishes, before starting a 4th different thread, you can initialise the semaphore to 0. Before running the 4th thread, you acquire the semaphore, but cannot, since it is 0. When one of the 3 threads finishes, it releases the semaphore, allowing the 4th thread to run.

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Semaphore;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Semaphore sem = new Semaphore(0);
        char[] letters = {'a', 'b', 'c'};
        List<Thread> threadList = new ArrayList<>();

        for (int threadNum = 0; threadNum < 3; threadNum  ) {
            int finalThreadNum = threadNum;
            Thread t = new Thread(() -> {
                for (int i = 0; i < 10; i  ) {
                    System.out.print(letters[finalThreadNum]);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                sem.release();
            });

            threadList.add(t);
            t.start();

        }


        Thread lastThread = new Thread(() -> {
            for (int i = 0; i < 10; i  ) {
                System.out.print('d');
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        sem.acquire();
        lastThread.start();

        for (Thread t : threadList) {
            t.join();
        }
        lastThread.join();
    }
}

CodePudding user response:

You can use a Semaphore. The first 3 threads will take all the permits and when the first one releases a permit, the 4th will pick it up and run.

public static void main(String [] args) throws IOException {
    Semaphore semaphore = new Semaphore(3, true);
    
    
    Runnable a = new Runnable() {   
        @Override
        public void run() {
            try {
                semaphore.acquire();
                for (int i = 0; i < 50; i  ) {
                    System.out.println("A");
                }                   
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                semaphore.release();
            }
        }
    };
    Runnable b = new Runnable() {   
        @Override
        public void run() {
            try {
                semaphore.acquire();
                for (int i = 0; i < 50; i  ) {
                    System.out.println("B");
                }                   
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                semaphore.release();
            }
        }
    };
    Runnable c = new Runnable() {   
        @Override
        public void run() {
            try {
                semaphore.acquire();
                for (int i = 0; i < 50; i  ) {
                    System.out.println("C");
                }                   
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                semaphore.release();
            }
        }
    };
    
    Runnable d = new Runnable() {   
        @Override
        public void run() {
            try {
                semaphore.acquire();
                System.out.println("D");
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                semaphore.release();
            }
        }
    };
    
    ExecutorService exec = Executors.newFixedThreadPool(5);
    exec.execute(a);
    exec.execute(b);
    exec.execute(c);
    exec.execute(d);
}

CodePudding user response:

What if the 4th thread's semaphore.acquire() happens before the other 3 threads' acquires...

You could use a CountDownLatch for that. Building on @Ryan 's answer:

    public static void main(String [] args) throws IOException {
        Semaphore semaphore = new Semaphore(3, true);
        CountDownLatch okToStartTaskD = new CountDownLatch(3);
        
        Runnable a = new Runnable() {   
            @Override
            public void run() {
                try {
                    semaphore.acquire();
                    okToStartTaskD.countDown();
                    ...
                }
                ...
            }
        };
        Runnable b = new Runnable() {   
            @Override
            public void run() {
                try {
                    semaphore.acquire();
                    okToStartTaskD.countDown();
                    ...                 
                }
            ...
            }
        };
        ...
        
        ExecutorService exec = Executors.newFixedThreadPool(5);
        exec.execute(a);
        exec.execute(b);
        exec.execute(c);
        try {
            okToStartTaskD.await();
        }
        catch (InterruptedException ex) {
            ex.printStackTrace();
            ...should never get here, but what if?...
        }
        exec.execute(d);
    }
  • Related