我有一个java应用程序,其中主线程启动2个其他线程。 如果其中一个线程终止,则主线程可能会启动另一个线程,具体取决于终止线程的结果。
实施例: 主线程创建2个线程:A和B.线程A将加载图片,线程B将加载另一个图片。如果A终止并成功加载图片,将创建一个新的Thread C,它会执行其他一些操作等等。
我该怎么做?我不想在主线程中使用忙等待,并且如果两个线程中的一个已经完成则每100ms检查一次。 我想我不能使用线程池,因为活动线程的数量(在这种情况下是A和B)会有很大变化,而且它是创建新线程的主线程决定。
这是“忙碌等待”解决方案的粗略草图:
public class TestThreads {
private class MyThread extends Thread {
volatile boolean done = false;
int steps;
@Override
public void run() {
for (int i=0; i<steps; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException exc) { }
}
done = true;
synchronized (this) {
notify();
}
}
public void waitFor(long ms) {
synchronized (this) {
try {
wait(ms);
} catch (InterruptedException exc) { }
}
}
}
public void startTest() {
MyThread a = new MyThread();
a.steps = 6;
a.start();
MyThread b = new MyThread();
b.steps = 3;
b.start();
while (true) {
if (!a.done) {
a.waitFor(100);
if (a.done) {
System.out.println("C will be started, because A is done.");
}
}
if (!b.done) {
b.waitFor(100);
if (b.done) {
System.out.println("C will be started, because B is done.");
}
}
if (a.done && b.done) {
break;
}
}
}
public static void main(String[] args) {
TestThreads test = new TestThreads();
test.startTest();
}
}
答案 0 :(得分:3)
这听起来像是使用ThreadPoolExecutor同时执行任务并使用ExecutorCompletionService包装它来收集结果的经典案例。
例如,假设 tasks 包含一组要并行执行的任务,每个任务在终止时返回一个String值,处理结果的代码在可用时可以是:
List<Callable<String>> tasks = ....;
Executor ex = Executors.newFixedThreadPool(10);
ExecutorCompletionService<String> ecs = new ExecutorCompletionService<String>(ex);
for (Callable<String> task : tasks)
ecs.submit(task);
for(int i = 0; i < tasks.size(); i++) {
String result = ecs.take().get();
//Do something with result
}
如果您将任务的标识作为返回值的一部分包含在内,那么您可以根据完成顺序做出决定。
答案 1 :(得分:2)
答案 2 :(得分:1)
您应该使用线程池。在线程池中,您有一定数量的线程,任务保存在队列中;只要线程可用,任务就从队列中取出并由该线程执行。
以下是有关线程池Sun tutorial的链接。
编辑:刚刚注意到您在答案中写道,您认为自己无法使用线程池。我不明白为什么会这样。如果您担心创建开销,可以将线程设置为按需创建,而不是一次创建,一旦创建了空闲线程,实际上不会伤害任何内容。
你还说这是主线程决定是否创建一个新线程,但它真的需要吗?我认为这可能会让你感到过于复杂。
答案 3 :(得分:0)
是否有理由直接控制线程执行而不是使用类似的东西 ExecutorService?
答案 4 :(得分:0)
代码的很多复杂性是主线程试图等待两个不同的对象。没有什么说你不能在另一个对象上使用wait和notify,如果你的任务是(A或B)然后是C,下面的代码将起作用 - 等待一个被设置的引用来指示完成的第一个任务。
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.*;
public class BiggieThreads
{
private static class MyTask implements Runnable
{
final int steps;
final AtomicReference<MyTask> shared;
final String name;
MyTask ( int steps, AtomicReference<MyTask> shared, String name )
{
this.shared = shared;
this.steps = steps;
this.name = name;
}
@Override
public void run()
{
for ( int i = 1; i <= steps; i++ ) {
System.out.println ( "Running: " + this + " " + i + "/" + steps);
try {
Thread.sleep ( 100 );
} catch ( InterruptedException exc ) { }
}
// notify if this is the first to complete
if ( shared.compareAndSet ( null, this ) )
synchronized ( shared ) {
shared.notify();
}
System.out.println ( "Completed: " + this );
}
@Override
public String toString ()
{
return name;
}
}
public void startTest() throws InterruptedException
{
final ExecutorService pool = Executors.newFixedThreadPool ( 3 );
final AtomicReference<MyTask> shared = new AtomicReference<MyTask>();
Random random = new Random();
synchronized ( shared ) {
// tasks launched while lock on shared held to prevent
// them notifying before this thread waits
pool.execute ( new MyTask ( random.nextInt ( 5 ) + 3, shared, "a" ) );
pool.execute ( new MyTask ( random.nextInt ( 5 ) + 3, shared, "b" ) );
shared.wait();
}
System.out.println ( "Reported: " + shared.get() );
pool.shutdown();
}
public static void main ( String[] args ) throws InterruptedException
{
BiggieThreads test = new BiggieThreads ();
test.startTest();
}
}
我倾向于在生产中使用信号量来完成这项工作,因为虽然等待很简单,但在信号量中使用会给行为命名,所以下次读代码时可以解决这个问题。