7
votes

futur.isdone retourne faux même si la tâche est terminée

J'ai une situation difficile, futur.isdone () code> retourne false code>, même si le thread est terminé.

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class DataAccessor {
    private static ThreadPoolExecutor executor;
    private int timeout = 100000;
    static {
        executor = new ThreadPoolExecutor(10, 10, 1000, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1000));
    }

    public static void main(String[] args) {
        List<String> requests = new ArrayList<String>();
        for(int i=0; i<20; i++){
            requests.add("request:"+i);
        }
        DataAccessor dataAccessor = new DataAccessor();

        List<ProcessedResponse> results = dataAccessor.getDataFromService(requests);
        for(ProcessedResponse response:results){
            System.out.println("response"+response.toString()+"\n");
        }
        executor.shutdown();
    }

    public List<ProcessedResponse> getDataFromService(List<String> requests) {
        final CountDownLatch latch = new CountDownLatch(requests.size());
        List<SubmittedJob> submittedJobs = new ArrayList<SubmittedJob>(requests.size());
        for (String request : requests) {
            Future<ProcessedResponse> future = executor.submit(new GetAndProcessResponse(request, latch));
            submittedJobs.add(new SubmittedJob(future, request));
        }
        try {
            if (!latch.await(timeout, TimeUnit.MILLISECONDS)) {
                // some of the jobs not done
                System.out.println("some jobs not done");
            }
        } catch (InterruptedException e1) {
            // take care, or cleanup
            for (SubmittedJob job : submittedJobs) {
                job.getFuture().cancel(true);
            }
        }
        List<ProcessedResponse> results = new LinkedList<DataAccessor.ProcessedResponse>();
        for (SubmittedJob job : submittedJobs) {
            try {
                // before doing a get you may check if it is done
                if (!job.getFuture().isDone()) {
                    // cancel job and continue with others
                    job.getFuture().cancel(true);
                    continue;
                }
                ProcessedResponse response = job.getFuture().get();
                results.add(response);
            } catch (ExecutionException cause) {
                // exceptions occurred during execution, in any
            } catch (InterruptedException e) {
                // take care
            }
        }
        return results;
    }

    private class SubmittedJob {
        final String request;
        final Future<ProcessedResponse> future;

        public Future<ProcessedResponse> getFuture() {
            return future;
        }

        public String getRequest() {
            return request;
        }

        SubmittedJob(final Future<ProcessedResponse> job, final String request) {
            this.future = job;
            this.request = request;
        }
    }

    private class ProcessedResponse {
        private final String request;
        private final String response;

        ProcessedResponse(final String request, final String response) {
            this.request = request;
            this.response = response;
        }

        public String getRequest() {
            return request;
        }

        public String getResponse() {
            return response;
        }

        public String toString(){
            return "[request:"+request+","+"response:"+ response+"]";
        }
    }

    private class GetAndProcessResponse implements Callable<ProcessedResponse> {
        private final String request;
        private final CountDownLatch countDownLatch;

        GetAndProcessResponse(final String request, final CountDownLatch countDownLatch) {
            this.request = request;
            this.countDownLatch = countDownLatch;
        }

        public ProcessedResponse call() {
            try {
                return getAndProcessResponse(this.request);
            } finally {
                countDownLatch.countDown();
            }
        }

        private ProcessedResponse getAndProcessResponse(final String request) {
            // do the service call
            // ........
            if("request:16".equals(request)){
                throw (new RuntimeException("runtime"));
            }
            return (new ProcessedResponse(request, "response.of." + request));
        }
    }
}


12 commentaires

Votre code n'a pas de sens. PooledExecutor.submit (LCALLABLE) Ne fonctionne pas, où instanciez-vous le appelable ? Où créez-vous le loquet et où attendez-vous?


Mon soupçon est que les appelables peuvent lancer une exception non capturée, ce qui aborde par conséquent les threads du travailleur, cependant comptez également le loquet avant de quitter appel .


@ Pétertörök ​​qui ne vient pas sous exécutionException? Pouvez-vous s'il vous plaît expliquer?


Vous obtenez seulement une exception Execitionnée si vous appelez Future.get (). J'utiliserais pour (futur Future: Futures) Future.get (); au lieu d'utiliser un décompte vers le bas ou une vérification de l'ISDONE ().


@YADAB, je veux dire que vous n'atteignez aucune exception dans appel , ni définir aucun gestionnaire pour par exemple. exceptions non capturées. Donc, si par exemple. Une exception de pointeur nulle est lancée dans appel , elle sera propagée vers le haut, abandonnant finalement le fil de travail sans rien remarquer.


YADAB Pouvez-vous nous montrer un cas de test de travail?


@ Pétertörök ​​Merci de réponse, mon doute et Javadoc dit s'il y a une exception que l'avenir devrait être mis à faire? Ou n'est-il pas mis à faire s'il existe une exception d'exécution?


@Johnvint pouvez-vous s'il vous plaît expliquer "cas de test de travail"?


Pouvez-vous écrire un cas de test avec une méthode simple principale () que si nous devions exécuter, nous verrions ce que vous voyez.


@YADAB, j'ai aussi vérifié le Javadoc depuis et cela suggère en effet que ma suspicion est fausse. Je recommande toujours de faire face aux exceptions d'une manière ou d'une autre si vous utilisez des threads.


@Johnvint a fourni un exemple détaillé.


Ce bogue se produit-il à chaque fois ou seulement parfois?


4 Réponses :



3
votes

Comme Jtahlborn a mentionné qu'il s'agit probablement d'une condition de race dans laquelle le compte-vient-fond signale ses fils d'attente, que les threads d'attente évaluent la condition d'annulation de l'avenir avant que le futuretask termine son exécution (qui se produira à un moment donné après le compte à rebours < / code>).

Vous ne pouvez tout simplement pas compter sur les mécanismes de synchronisation du compte-comteThlatch pour être synchronisé avec les mécanismes de synchronisation d'un avenir. Ce que vous devriez faire, c'est compter sur le futur pour vous dire quand c'est fait.

Vous pouvez futur.get (long timeout, timeunit.milliseconds) au lieu de compte à rebours à compter à niveaux.millisecondes) . Pour obtenir le même type d'effet que le loquet, vous pouvez ajouter tous les futur s à une liste , itérer sur la liste et sur chaque avenir.


0 commentaires

2
votes

Voici le scénario de la condition de course:

  • Le thread principal est dans latch.await , il ne reçoit aucune machine à sous CPU du planificateur Java pour Millisecondes
  • Les derniers appels de threads exécutants comptedidlatch.Countdown () dans le enfin clause
  • Le planificateur Java décide de donner plus de priorité au fil principal car il a été aussi attendu pendant un moment
  • En conséquence, lorsqu'il demande le dernier résultat futur , il n'est pas encore disponible car le dernier fil d'exécuteur n'a pas de tranches de temps pour propager le résultat, il est toujours dans enfin ...

    Je n'ai pas trouvé d'explication détaillée sur la manière dont le planificateur Java fonctionne vraiment, probablement parce que cela dépend principalement du système d'exploitation exécutant la JVM, mais de manière générale, il essaie de donner également à la CPU aux fils endurlables en moyenne sur un période de temps. C'est pourquoi le thread principal peut atteindre le test iSdone avant que l'autre ait laissé le enfin clause.

    Je propose que vous modifiez vos résultats. latch.await . Comme vous le savez, le loquet a été diminué à zéro (sauf si le fil principal a été interrompu), tous les résultats doivent être disponibles vraiment bientôt. La méthode d'obtention avec le délai d'attente Laissez le planificateur la chance d'affecter une tranche de temps au dernier fil toujours en attente de la clause enfin: xxx

    une remarque: Votre code n'est pas réaliste que la méthode gettandProcessResponse se termine par moins d'un millisecondes. Avec un sommeil aléatoire là-bas, la condition de course ne sort pas si souvent.


0 commentaires

0
votes

I Deuxièmement les opinions sur les conditions de course. Je suggérerais d'oublier le loquet et l'utilisation java.util.concurrent.threadpoolEcutor.awaitTermination (long, timeunit)


0 commentaires