Почему программа не завершается?

#java #multithreading #concurrency #executorservice

#java #многопоточность #параллелизм #executorservice

Вопрос:

Хорошо, итак, я пытался преобразовать блокирующий сетевой запрос в неблокирующий запрос. Библиотека, которую я использую для сетевого ввода-вывода, предоставляет функции для выполнения асинхронных HTTP-вызовов, но в любом случае, ради экспериментов я попытался сделать это таким образом:

 import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.http.exceptions.UnirestException;
import com.mashape.unirest.request.GetRequest;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class TestExecutorService {
    private static final ExecutorService executor = Executors.newSingleThreadExecutor();
    static volatile Thread innerThread;

    public static void asyncGet (String url) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                innerThread = Thread.currentThread();
                GetRequest request = Unirest.get(url);
                try {
                    HttpResponse <String> response = request.asString();
                    System.out.println(response.getBody());
                    Unirest.shutdown();
                } catch (UnirestException exc) {
                    exc.printStackTrace();
                } catch (IOException ioe) {
                    ioe.printStackTrace();
                }
            }
        });
    }
}
public class Main {
    public static void main(String[] args) {
        TestExecutorService.asyncGet("https://stackoverflow.com");
        System.out.println("We're already here!");

        try {
            // Delay so that executor service's thread object could be
            // ...assigned to static variable innerThread
            Thread.sleep(100);
            TestExecutorService.innerThread.join();
        } catch (InterruptedException ie) {
            ie.printStackTrace();
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }
}
  

Я не профессиональный программист и абсолютный новичок, когда дело доходит до параллелизма, и даже я мог бы сказать, что этот фрагмент кода можно улучшить, по крайней мере немного (одно из тех чувств, которые испытываешь, будучи новичком, когда знаешь, что что-то не так, но не уверен, что именно). В любом случае, что меня смущает в приведенном выше коде, так это то, что программа не завершается. Я не ожидал, что это произойдет. Я немного прочитал о Executors.singleThreadExecutor , и у меня есть идея, что если внутренний поток по какой-либо причине прекращает работу, он создает новый поток и безопасно «переносит» состояние во вновь созданный поток. Я понятия не имею, почему программа не завершается. Может кто-нибудь дать несколько советов?

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

Ответ №1:

Вы смешиваете два шаблона.

Если вы используете executor s, вам не нужно присоединяться. Этот поток был запущен системным потоком, а не вашим основным потоком. Это не ваш дочерний поток, на самом деле вы не можете join использовать его. Просто запустите и забудьте.

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

Комментарии:

1. Спасибо. Удаление innerThread переменной и вызов shutdown on executor после вызова asyncGet в main, похоже, решает проблему.

Ответ №2:

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

 Executors.newFixedThreadPool(1, 
  new ThreadFactory() {
    public Thread newThread(Runnable r) {
      Thread t = Executors.defaultThreadFactory.newThread(r);
      t.setDaemon(true);
      return t;
   }
});
  

Это приведет к созданию службы-исполнителя с потоками демона.
У вас также есть небольшое состояние гонки с вашим «sleep».
Вместо join используйте CountdownLatch .

Комментарии:

1. ДА… Я осведомлен о возможном состоянии гонки, но поскольку программа достаточно мала, и я получу исключение NullPointerException, если это произойдет, я решил перевести поток в спящий режим на некоторое время. В любом случае, спасибо. Мне придется посмотреть, что такое потоки демона 🙂