求助大佬解决如下线程问题

Little5 2019-12-01 05:03:39
本人初学java,在练习newSingleThreadExecutor的时候发现一个很奇怪的问题。
下面的代码在运行的时候每次的输出都是:
run2
run1
task2

但是理论上输出应该还包含run3,而且顺序也应该是随机的invokeAny()的返回结果也应该是task1,task2,task3里随机一个,为什么每次都是一样的task2?
同样的代码,我把newSingleThreadExecutor改成newFixedThreadPool(3),或者把invokeAny()改成invokeAll()结果就是正常的。
请各位大佬帮忙看看是什么问题。非常谢谢!

完整代码如下:


package ThreadPool;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class PoolTest
{

public static void main(String[] args) throws InterruptedException, ExecutionException
{
ExecutorService exe = Executors.newSingleThreadExecutor();
Set<Callable<String>> call = new HashSet<>();

call.add(new Callable<String>()
{
@Override
public String call() throws Exception
{
System.out.println("run1");
return "task1";
}
});

call.add(new Callable<String>()
{
@Override
public String call() throws Exception
{
System.out.println("run2");
return "task2";
}
});

call.add(new Callable<String>()
{
@Override
public String call() throws Exception
{
System.out.println("run3");
return "task3";
}
});

System.out.println(exe.invokeAny(call));
exe.shutdown();
}
}
...全文
373 10 打赏 收藏 转发到动态 举报
写回复
用AI写文章
10 条回复
切换为时间正序
请发表友善的回复…
发表回复
SZ深呼吸 2019-12-03
  • 打赏
  • 举报
回复
执行结束也是要有时间的,结果是随机的,多次运行有不同的结果,我还遇到过三个都执行了的情况。不必在这里太钻牛角尖吧,要学的东西太多太多。
jiawenhe123 2019-12-03
  • 打赏
  • 举报
回复
这是inovkeAny的实际调用逻辑:
/**
     * the main mechanics of invokeAny.
     */
    private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
                              boolean timed, long nanos)
        throws InterruptedException, ExecutionException, TimeoutException {
        if (tasks == null)
            throw new NullPointerException();
        int ntasks = tasks.size();
        if (ntasks == 0)
            throw new IllegalArgumentException();
        ArrayList<Future<T>> futures = new ArrayList<Future<T>>(ntasks);
        ExecutorCompletionService<T> ecs =
            new ExecutorCompletionService<T>(this);

        // For efficiency, especially in executors with limited
        // parallelism, check to see if previously submitted tasks are
        // done before submitting more of them. This interleaving
        // plus the exception mechanics account for messiness of main
        // loop.

        try {
            // Record exceptions so that if we fail to obtain any
            // result, we can throw the last exception we got.
            ExecutionException ee = null;
            final long deadline = timed ? System.nanoTime() + nanos : 0L;
            Iterator<? extends Callable<T>> it = tasks.iterator();

            // Start one task for sure; the rest incrementally
            futures.add(ecs.submit(it.next()));
            --ntasks;
            int active = 1;

            for (;;) {
                Future<T> f = ecs.poll();
                if (f == null) {
                    if (ntasks > 0) {
                        --ntasks;
                        futures.add(ecs.submit(it.next()));
                        ++active;
                    }
                    else if (active == 0)
                        break;
                    else if (timed) {
                        f = ecs.poll(nanos, TimeUnit.NANOSECONDS);
                        if (f == null)
                            throw new TimeoutException();
                        nanos = deadline - System.nanoTime();
                    }
                    else
                        f = ecs.take();
                }
                if (f != null) {
                    --active;
                    try {
                        return f.get();
                    } catch (ExecutionException eex) {
                        ee = eex;
                    } catch (RuntimeException rex) {
                        ee = new ExecutionException(rex);
                    }
                }
            }

            if (ee == null)
                ee = new ExecutionException();
            throw ee;

        } finally {
            for (int i = 0, size = futures.size(); i < size; i++)
                futures.get(i).cancel(true);
        }
    }
实际上调用的方法是由执行集合的iterator过程决定的,也就是第一个取得的Callable对象。 对于HashSet就也是依靠其hash散列的结果来决定的,如果你要测试这个过程,你可以重写hashCode方法,如:
    @Override
			public int hashCode(){
				return 2;
			}
Java并发包提供了诸多的快捷实现,调用invokeAny意图意味着,你要执行一个任务,该任务有多个实现方式,就是所谓的路径, 一旦一条路径达到终点,则取消其他的执行。而通常的hello world玩具式调用不具备这样的特性,所以也不用太纠结。找到场景才是学习多线程的有效方法。
helloworddm 2019-12-02
  • 打赏
  • 举报
回复
Executes the given tasks, returning the result of one that has completed successfully (i.e., without throwing an exception), if any do. Upon normal or exceptional return, tasks that have not completed are cancelled. The results of this method are undefined if the given collection is modified while this operation is in progress. 这个应该是原因.至于你说的那个随机性问题,目前还没找到原因.你可以看下invokeAny的源码
qq_39936465 2019-12-02
  • 打赏
  • 举报
回复
newFixedThreadPool(3)你指定3个线程了,所以3个线程运行完才会结束。
qq_39936465 2019-12-02
  • 打赏
  • 举报
回复
引用 楼主 Little5 的回复:
[size=18px]本人初学java,在练习newSingleThreadExecutor的时候发现一个很奇怪的问题。 下面的代码在运行的时候每次的输出都是:
应该是main中已经关闭线程池了,但是run1还没运行完,run3还没开始运行。这个有随机性,说不定改天运行run2或run1没有运行到
qq_39936465 2019-12-02
  • 打赏
  • 举报
回复
在API中 ExecutorService中invokeAny方法下有这样的说明: Upon normal or exceptional return, tasks that have not completed are cancelled. 所以在单线程池模式下,未开始的第3个线程被取消了。
qq_39936465 2019-12-02
  • 打赏
  • 举报
回复
引用 6 楼 Little5 的回复:
你好,如您所说,那输出结果也应该只有一个run啊,为什么会有两个。而且我明明添加了3个任务,却只有两个。很困惑。而且invokeAny的API文档里说的是“执行所给定的任务,返回值是其中一个任务的执行结果”,和您说的有点区别啊,能详细解释下么?谢谢
测试了一下你用invokeAny只能输出2个线程,第3个线程可能被中断了。

public class RunMain1 {
    public static void main(String[] args) throws ExecutionException,InterruptedException{
    	Set<Callable<String>> call = new HashSet<>();
        call.add(new MyCallableA());
        call.add(new MyCallableB());
        call.add(new MyCallableC());
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        System.out.println(executorService.invokeAny(call));
       
    }
}

class MyCallableA implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("CallableA begin");
 
        for (int i = 0; i < 5; i++) {
            System.out.println("A:" + i);
        }
 
        System.out.println("CallableA end" );
 
        return "call A";
    }
}

class MyCallableB implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("CallableB begin");
 
        for (int i = 0; i < 5; i++) {
            System.out.println("B:" + i);
        }
 
        System.out.println("CallableB end");
 
        return "call B";
    }
}


class MyCallableC implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("CallableC begin");
 
        for (int i = 0; i < 5; i++) {
            System.out.println("C:" + i);
        }
 
        System.out.println("CallableC end");
 
        return "call C";
    }
}
输出结果:

CallableB begin
B:0
B:1
B:2
B:3
B:4
CallableB end
CallableA begin
call B
A:0
A:1
A:2
A:3
A:4
CallableA end
Little5 2019-12-02
  • 打赏
  • 举报
回复
引用 5 楼 baiydn 的回复:
invokeAny取得第一个方法的返回值,当第一个任务结束后,会调用interrupt方法中断其它任务。 invokeAll等线程任务执行完毕后,取得全部任务的结果值。 你要所有任务执行就用invokeAll
你好,如您所说,那输出结果也应该只有一个run啊,为什么会有两个。而且我明明添加了3个任务,却只有两个。很困惑。而且invokeAny的API文档里说的是“执行所给定的任务,返回值是其中一个任务的执行结果”,和您说的有点区别啊,能详细解释下么?谢谢
SZ深呼吸 2019-12-02
  • 打赏
  • 举报
回复
invokeAny取得第一个方法的返回值,当第一个任务结束后,会调用interrupt方法中断其它任务。 invokeAll等线程任务执行完毕后,取得全部任务的结果值。 你要所有任务执行就用invokeAll
Little5 2019-12-02
  • 打赏
  • 举报
回复
引用 2 楼 qq_39936465 的回复:
[quote=引用 楼主 Little5 的回复:] [size=18px]本人初学java,在练习newSingleThreadExecutor的时候发现一个很奇怪的问题。 下面的代码在运行的时候每次的输出都是:
应该是main中已经关闭线程池了,但是run1还没运行完,run3还没开始运行。这个有随机性,说不定改天运行run2或run1没有运行到[/quote] 大佬,你好。请问为什么任务都还没运行完,main就关闭线程池了呢?但是shutdown()的原理就是不接受新任务,但会等待当前已提交的线程执行完毕才会关闭。而且如果不加shutdown(),就算关闭整个程序,线程池还是运行的。我一直就很困惑这点,明明添加了3个任务。但每次都只运行两个。

62,621

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧