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);
}