Почему SocketTimeoutException заставляет мою программу зависать?

Я даю своей программе более 600 ссылок, хранящихся в ArrayList, чтобы получить заголовок веб-страницы, используя JSoup (среди прочего). Для каждой ссылки (используя цикл for) я создаю новый поток (используя thread.start()) и передаю ссылку через свою программу, я жду завершения моего потока (с thread.join) перед запуском нового потока (одновременное выполнение вызовет некоторые проблемы, я сделал это, чтобы предотвратить неожиданное завершение потока, чтобы остановить выполнение для других ссылок).

Проблема в том, что иногда JSoup выдает исключение SocketTimeoutException (которое я должен поймать), из-за чего моя программа зависает. Я не знаю, почему выполнение останавливается даже в окружении try/catch.

Вот кусок моего кода, возможно, это поможет вам понять:

// In the method actionPerformed() of my JPanel

for(final String link : links)
{
    Thread t = new Thread()
    {
        public void run()
        {
            Analyzer.process(link);
        }
    };
    t.start();
    try 
    {
        t.join();
    } 
    catch (InterruptedException e) 
    {
        e.printStackTrace();
    }
}

И в моем процессе:

// method process() of my Analyzer class
try 
{
    Document doc = Jsoup.connect(lien).userAgent("Mozilla").timeout(5*10000).get(); 
    //                    ^ EXCEPTION THROWN HERE ! ^

    title = doc.title();
}
catch (Exception e) 
{
    e.printStackTrace();
    erreurs+="Erreur lors de la lecture du titre\n";
}

Это раздражает, потому что процесс очень долгий, я оставил его работать на ночь и сегодня обнаружил, что моя программа зависла на 54-й ссылке. ^^' Заранее спасибо!

ИЗМЕНИТЬ – ОБНОВИТЬ

SercanOzdemir предложил мне использовать ExecutorService вместо создания потоков и выполнения start()-join(), поэтому я попробовал:

ExecutorService ex = Executors.newSingleThreadExecutor();
for(final String link : links)
{
    System.err.println("-- "+i+" --");              //DEBUG
    ex.execute(new Runnable(){
            @Override
            public void run(){
                try
                {
                    Analyzer.process(link);
                }
                catch( Exception e )
                {
                    e.printStackTrace();
                }

            }
    });
    i++;                                            //DEBUG
    }
ex.shutdown();

Но он печатает только мои отладочные ссылки. Любая идея, почему он не запускает мой процесс?


person Malik    schedule 29.04.2015    source источник
comment
В. Какой смысл запускать поток только для того, чтобы немедленно join() его join() не делать в промежутке? А. Нет.   -  person user207421    schedule 29.04.2015
comment
@EJP Если я использую только один поток для выполнения моего процесса по 600 ссылкам, единственное прерывание потока остановит весь процесс (у меня уже есть эта проблема), и если я не буду выполнять каждый процесс один за другим, я У меня другая проблема (особенно с обработкой некоторых файлов). Я понимаю, что вы думаете, но это был единственный способ решить проблему с моим знанием. :)   -  person Malik    schedule 29.04.2015


Ответы (2)


Я не пробовал это с jsoup, но это простой способ создания потоков для выполнения задачи и отслеживания их состояния.

    ExecutorService executorService = Executors.newCachedThreadPool();
    Future future = null;
    for(final String link : links)
    {
        future = executorService.submit(new Runnable(){

        @Override
        public void run(){
            try{
               Analyzer.process(link);
            }
            catch( Exception e ){
                e.printStackTrace();
            }

        }
    });

        while(future != null
                && !future.isDone()
                && !future.isCancelled())
        {
            try {
                Thread.sleep(2000); // Or do something else
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    executorService.shutdown();

Код в вашем обновленном вопросе не будет работать, потому что вы создаете службу и мгновенно перезаписываете ее, не дожидаясь ее завершения.

ExecutorService ex = Executors.newSingleThreadExecutor();
for(final String link : links)
{
  System.err.println("-- "+i+" --");              //DEBUG
  ex.execute(new Runnable(){ //Here it is created
        @Override
        public void run(){
            try
            {
                Analyzer.process(link);
            }
            catch( Exception e )
            {
                e.printStackTrace();
            }

        }
});
i++;                                            //DEBUG
//a split second later this loop finishes and overwrites the service again
}
ex.shutdown();
person Greg King    schedule 29.04.2015
comment
Этот цикл while - довольно уродливое решение... Просто вызовите executerService.awaitTermination() - person Victor; 29.04.2015
comment
Конечно, это некрасиво, но executerService.awaitTermination(); полезен только в том случае, если ему больше нечего делать в основном потоке. Если это программа на основе пользовательского интерфейса, он может обновлять строку состояния и т. Д. - person Greg King; 29.04.2015
comment
@Victor Он хорошо работает с циклом while, может быть, было бы лучше использовать awaitTermination(), однако у него есть 2 параметра, поэтому метод принимает: executorService.awaitTermination(long, TimeUnit), так что мне лучше передать в качестве параметров в моем случае? - person Malik; 29.04.2015
comment
executorService.awaitTermination() не заменяет цикл while. Цикл while проверяет, не работает ли Runnable(); завершено. executorService.awaitTermination() сообщает ExecutorService больше не принимать Runnables и ждать выхода запущенных потоков. - person Greg King; 29.04.2015
comment
@GregKing, хорошо, но в этом случае основной поток все равно усыплялся. Что касается await(): например, (1000, TimeUnit.Miliseconds)``, (5, TimeUnit.Minutes)` и т. д. - person Victor; 29.04.2015
comment
Цикл while - плохая практика, поскольку он позволяет обрабатывать одну ссылку за раз... - person Victor; 29.04.2015
comment
@Victor Действительно, я не могу взаимодействовать со своим фреймом во время выполнения процесса, поэтому я буду использовать await (). Есть ли способ, с помощью которого я все еще мог бы взаимодействовать с моим фреймом? - person Malik; 29.04.2015
comment
@Victor Я пробовал await (), но на самом деле мне нужен цикл while, так как он ждет завершения задачи перед запуском новой задачи, это то, что мне нужно в моей программе. - person Malik; 29.04.2015

Открывать новую тему для каждой ссылки — очень плохой вариант.

Рассмотрите возможность использования ExecutionService, если хотите многопоточная программа.

Если мы подойдем к вашей проблеме, я бы предпочел изменить эту программу точно так, как показано ниже, потому что вы перехватываете исключение в части соединения, и это не гарантирует, что ваше исключение было выбрано в этой части.

new Runnable(){

            @Override
            public void run(){
                try{
                    // your codes
                }
                catch( Exception e ){
                    e.printStackTrace();
                }

            }
        }.run();
person Sercan Ozdemir    schedule 29.04.2015
comment
Я не знал о существовании ExecutionService. Я не знаю, как это работает, но это кажется очень многообещающим, поскольку я могу взаимодействовать с моей JPanel, поэтому я бы предпочел попробовать использовать эту опцию. Итак, я бы создал новую службу-исполнитель вне моего цикла for (ExecutorService ex = Executors.newSingleThreadExecutor();), затем в моем цикле я бы написал: ex.execute(new Runnable() { [...] }); и после моего цикла, закрывающегося с помощью ex.shutdown(), это правильно? - person Malik; 29.04.2015
comment
И добавьте ex.waitForTermination()`, иначе некоторые из отправленных задач могут не выполняться. - person Victor; 29.04.2015
comment
Кроме того, если ваш рабочий реализует Callable вместо Runnable, вы можете легко вернуть результат операции. - person Victor; 29.04.2015
comment
да, это правильно, если вы хотите, чтобы он работал в фоновом режиме. - person Sercan Ozdemir; 29.04.2015