【问题标题】:Using Thread.join() method使用 Thread.join() 方法
【发布时间】:2011-06-21 13:38:14
【问题描述】:

我是多线程编程的新手。使用 join 方法时出现意外行为。有时两个线程给出相同的结果,有时只显示一个结果,有时显示正确的结果。我做错了什么?

public class Client {
    public static void main(String args[]) throws Exception {
        String model = args[0];
        String property = args[1];
        String parameters = args[2];
        String wsdlPaths = args[3];
        int numServices = Integer.parseInt(args[4]);
        String[] parameter;
        getParameters(parameters, parameter);
        String[] wsdl;
        getWSDL(wsdlPaths, wsdl);       
        Thread[] t  = new Thread[numServices];
        ClientHelper[] nch = new ClientHelper[numServices];
        TestStub[] stub = new TestStub[numServices];
        TestStub.Experiment[] request = new TestStub.Experiment[numServices];
        TestStub.ExperimentResponse[] response = new TestStub.ExperimentResponse[numServices];            
        for (int i = 0; i < numServices; i++) {     
            stub[i] = new TestStub(wsdl[i]);
            request[i] = new TestStub.Experiment();
            request[i].setArgs0(model);
            request[i].setArgs1(property);
            request[i].setArgs2(parameter[i]);
            nch[i] = new ClientHelper(stub[i], request[i]);
            t[i] = new Thread(nch[i]);          
            t[i].start(); // When I moved this statement to the next loop just before the join method, the program behaved like a single threaded program and was working correctly.
        }
        for (int i = 0; i < numServices; i++) { 
            try {
                t[i].join();
            } catch (InterruptedException e) {
            }
            System.out.println(t[i].getName());
            response[i] = nch[i].response;
            System.out.println("Response " + i + " : " + response[i].get_return());                
        }
    }
}


public class ClientHelper implements Runnable {
    TestStub stub;
    TestStub.Experiment request;
    TestStub.ExperimentResponse response;

    public ClientHelper(TestStub stub, TestStub.Experiment request){
        this.stub = stub;
        this.request = request;
    }

    public void run() {
        try {
            response = stub.Experiment(request);
        }
        catch (Exception e) {
        }
    }
}

【问题讨论】:

  • 你有没有机会解决你的问题,并创建一个更小的测试用例?我看到的第一件事是您的一个线程可能会抛出异常,而您永远看不到它,因此会出现意外行为。在你的空 catch 块中添加一个 print 语句。
  • 不是您正在运行的代码。例如,这不会编译:String[] wsdl; getWSDL(wsdlPaths, wsdl);。请贴出真实代码。
  • @Jon 是的,这不是真正的代码,我正在运行,我试图尽可能多地剥离代码。真正的代码需要一些模型和属性文件以及可以执行这些文件的 Web 服务。所以你将无法运行它。我的主要问题是for (int i=0;i&lt;n;i++) { thread[i].start(); } for (int i=0;i&lt;n;i++) { thread[i].join(); //output variable set by thread[i]. } 在不同的运行中,打印出来的变量是不同的。有时它们是相同的,有时只打印其中一个,有时根本不打印。
  • 因此,将您的代码简化为可以演示问题的内容但会编译。否则,您很可能已经解决了问题。见tinyurl.com/so-hints。我的猜测是你有一些你不想要的共享状态,但如果你不能给我们任何现实的代码,我们就无法判断。

标签: java multithreading


【解决方案1】:

您的所有请求都使用相同的模型和属性。如果他们有某种状态 - 你会得到这种行为。

request[i].setArgs0(model);
request[i].setArgs1(property);

【讨论】:

  • 是的,模型和属性应该在所有线程中都相同(并且它们只能由 Web 服务读取),只是不同线程之间的参数应该不同。
【解决方案2】:
    t[i].join(); 

这并不像你想象的那样!它“暂停”了主要方法!它让 main 方法等待 t[i] 完成。之后,main 方法恢复该循环。

【讨论】:

  • 是的,我想要完全有这种行为,我想要启动所有线程,然后我不想让 main 方法等到所有线程都完成。
  • 正如我所说:main 方法立即等待第一个线程。你只在第一个线程完成后才调用第二个线程!您不是在循环中一次在所有线程上调用 join。
  • 是的,我明白了。所有线程的工作是相互独立的。所以在第一个循环中,我一个接一个地启动所有线程。在第二个循环中,我等待每个线程完成并使用其结果。因此,当主线程等待第一个线程完成时,所有线程都在做它们的工作,之后当主线程等待第二个线程时,所有剩余的线程都在做它们的工作。抱歉,在第二次阅读时,我觉得我之前的评论并没有传达我想要说/做的事情。
  • 不,这不是正在发生的事情。您正在等待第一个线程完成他们的工作,但第二个线程和所有其他线程继续运行。在第一个线程完成后,您的 for 循环将继续,但所有其他线程现在可能都已完成工作。调用 join 对你想要达到的目的没有意义......你为什么不等到所有线程都完成?
  • 如果一个线程完成并且我在它上面调用 join ,那么程序会立即进入下一条语句,所以通过不等到所有线程都完成我可以节省时间。
【解决方案3】:

与其直接使用线程,不如使用像ExecutorService这样的线程池。

final String[] parameter=  getParameters(parameters);
final String[] wsdl = getWSDL(wsdlPaths);
ExecutorService executor = Executors.newCachedThreadPool();
List<Future<TestStub.ExperimentResponse>> futures = new ArrayList<Future<TestStub.ExperimentResponse>>();
for (int j = 0; j < numServices; j++) {
    final int i = j;
    futures.add(executor.submit(new Callable<TestStub.ExperimentResponse>() {
        @Override
        public TestStub.ExperimentResponse call() throws Exception {
            TestStub stub = new TestStub(wsdl[i]);
            TestStub.Experiment request = new TestStub.Experiment();
            request.setArgs0(model);
            request.setArgs1(property);
            request.setArgs2(parameter[i]);
            return stub.Experiment(request);
        }
    }));
}
for (Future<ExperimentResponse> future : futures) {
    TestStub.ExperimentResponse response = future.get();
    System.out.println("Response: " + response.get_return());
}
executor.shutdown();

【讨论】:

    【解决方案4】:

    由于发布的代码无法编译,而且它可能太大而无法推断出根本原因,所以我会抓住机会并提示可能出了什么问题。

    您的代码包含两个 for 循环:

    for (int i = 0; i < numServices; i++) { 
        ...
        t[i].start();
    }
    
    for (int i = 0; i < numServices; i++) { 
        t[i].join();
        ...
    }
    

    你的观察已经推断出

    for (int i = 0; i < numServices; i++) { 
        t[i].start();
        t[i].join();
        ...
    }
    

    不会引起问题。

    嗯,那是因为您现在正在序列化进程,并且根本没有并发执行。我可以从中推断出的唯一一件事是,您正在跨线程共享状态,可能在 Runnable 的构造函数的 TestStubTestStub.Experiment 参数中。除非有要执行的代码,否则我无法说明共享的内容,但这肯定是原因。

    更新

    在考虑stub.Experiment(request); 声明之后,我还建议验证(生成的)Web 服务代理和 Web 服务客户端框架是否也是线程安全的。它们可能正在发布可能跨线程使用的对象引用,因此需要使用适当的锁进行显式同步。遗憾的是,没有简单的方法来确定一个库和一组生成的类是否是线程安全的。

    【讨论】:

    • 是的,我想stub.Experiment(request); 很可能有问题 BTW 我正在使用 Axis2 并通过 wsdl2java 命令生成客户端框架。
    • 很可能,但在某种程度上,using separate stubs in separate threads 可能会导致线程安全的实现。当然,这不应该让您相信您的代码中没有发生任何状态共享。
    • @Rohit,有没有办法可以发布 Web 服务的 WSDL?我可以模拟服务,看看你的客户端代码是否是线程安全的。
    • 我想通了。代码没有任何问题,因为我使用的是同一个存根类的不同实例,所以它是一个线程安全的实现。问题出在我编写的 Web 服务上。有两个 Web 服务,它们都将结果写入同一个文件!有时你所需要的只是新的一天。感谢大家的帮助!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2010-12-26
    • 1970-01-01
    • 2010-10-23
    • 1970-01-01
    • 2021-09-05
    • 2023-02-22
    • 1970-01-01
    相关资源
    最近更新 更多