повторное использование java-потока через исполнителя

Меня смущает следующее:
Самый простой способ использовать потоки в программе Java — расширить класс Thread и реализовать интерфейс runnable (или просто реализовать runnable).
Запустить выполнение потока. мы должны вызвать метод start() потока, который, в свою очередь, вызывает метод run() потока. Итак, поток запускается.
Метод start() (если я не ошибаюсь) должен вызываться точно и только один раз для каждого потока. В результате экземпляры потоков не могут быть повторно использованы, если только сам метод run не запускается в некотором смысле, если не считать бесконечного цикла, что облегчает пользовательскую реализацию повторного использования потока.
Теперь документ javadoc текст ссылки говорит

Вызовы для выполнения будут повторно использовать ранее созданные потоки, если они доступны.

Я не понимаю, как это реализовано. Я предоставляю в методе выполнения метода исполнителя свой собственный поток, например.

  ExecutorService myCachedPool = Executors.newCachedThreadPool();
  myCachedPool.execute(new Runnable(){public void run(){  
     //do something time consuming

  }});

Как можно повторно использовать этот пользовательский поток, который я делегирую инфраструктуре исполнителя?
Разрешено ли исполнителю вызывать метод start() более 1 раза, в то время как мы не можем этого делать в наших программах? Я что-то неправильно понимаю?

Спасибо.


person Cratylus    schedule 19.09.2010    source источник


Ответы (3)


Обратите внимание, что не Executor вызывает start(), а ExecutorService. И нет, он не звонит start() дважды. Он не запускает задачу, которую вы даете ему напрямую, используя Thread.start()... вместо этого он запускает поток, который знает об очереди работы этого пула потоков. Поток в основном будет ждать, пока не будет выполнена какая-то работа, затем возьмет ее и выполнит, прежде чем вернуться к ожиданию. Таким образом, хотя поток выполняет несколько задач, Thread.start() вызывается только один раз.

РЕДАКТИРОВАТЬ: Судя по комментариям, вы немного запутались в разнице между Runnable (это задача, которая должна быть выполнена) и Thread (то, что выполняет задачи).

Один и тот же поток может выполнять несколько задач. Рассмотрим очень простой пример без использования пула потоков:

public class MultiRunnable implements Runnable
{
    private final List<Runnable> runnables;

    public MultiRunnable(List<Runnable> runnables)
    {
        this.runnables = runnables;
    }

    public void run()
    {
        for (Runnable runnable : runnables)
        {
             runnable.run();
        }
    }
}

(Игнорируйте потенциальные проблемы с безопасностью потоков при использовании List<T> из нескольких потоков.)

Вы можете создать целую кучу Runnable задач, способных делать разные вещи, а затем создать одну MultiRunnable для выполнения их по очереди. Передайте этот экземпляр MultiRunnable в конструктор Thread, а затем, когда вы запустите поток, он выполнит каждую из исходных выполняемых задач. Это помогает?

person Jon Skeet    schedule 19.09.2010
comment
@Jon: Прости, что потерял тебя. Таким образом, внутренние потоки фреймворка используются повторно, а не исполняемый файл, который я передаю в качестве аргумента для выполнения? Мой поток, который я делегирую фреймворку, будет каждый раз повторно создаваться, но одним и тем же экземпляром внутреннего потока, созданным фреймворком? - person Cratylus; 19.09.2010
comment
@ user384706: Да. Ваш runnable не является потоком — это просто задача, которую нужно выполнить. Вам нужно различать их; они очень разные. - person Jon Skeet; 19.09.2010
comment
@Jon: Большое спасибо. Единственное, в чем я не уверен, так это в чем преимущество использования Executors.newCachedThreadPool(); Потому что, если мой класс, который реализует runnable (для задачи), требует больших затрат для создания экземпляра, он не будет повторно использоваться, и тот же поток фреймворка будет продолжать использовать новые экземпляры задачи. Итак, что я получу от этого API? Если только концепция не заключается в том, что каждый класс, реализующий runnable, минимален. - person Cratylus; 19.09.2010
comment
@user384706 user384706 Создание потока связано с относительно большими накладными расходами, и вы ничего не можете с этим поделать. Создание экземпляра класса очень дешевле по сравнению с ним (и если вы сделали его дорогим, например, обращение к базе данных/чтение файлов и т. д. в его конструкторе, вы можете реорганизовать его, в отличие от потоков, накладные расходы которых вы не могу контролировать) - person nos; 19.09.2010
comment
@nos: Значит, мои задачи не должны расширять Thread, а просто реализовывать runnable? Потому что, если расширить поток, относительно большие накладные расходы на создание потока все еще существуют. Правильно? - person Cratylus; 19.09.2010
comment
Спасибо, это действительно полезно. Но один дополнительный вопрос: как в таком случае произойдет GC, если поток никогда не будет завершен, все ли задачи, переданные потоку, останутся в памяти, даже если они завершили свое индивидуальное выполнение. Произойдет ли GC только после того, как поток каким-то образом будет уничтожен? - person Yash Agarwal; 12.10.2019
comment
@YashAgarwal: я думаю, было бы лучше задать новый вопрос с конкретным примером вашей конкретной ситуации. - person Jon Skeet; 12.10.2019

Он не вызывает start() более одного раза; вместо этого поток в пуле никогда не завершается, а просто остается в живых --- ждет. Исходный код доступен для загрузки, если вы хотите посмотреть на него.

Каждый поток в пуле потоков может просто wait() передать исполнителю новый Runnable, но собственный метод run() потока не завершен. Он просто ждет, пока новый Runnable будет передан исполнителю.

person jbindel    schedule 19.09.2010
comment
То есть вы имеете в виду, что в javadoc, где говорится, что ранее созданные потоки будут повторно использоваться, это относится не к исполняемым объектам, которые я передаю для выполнения, а к внутренним потокам структуры исполнителей? Итак, мой Runnable будет повторно создан тем же потоком пула потоков? Я понял, что вы говорите? - person Cratylus; 19.09.2010
comment
Ваш Runnable не будет восстановлен. Ваш экземпляр Runnable будет передан одному из потоков в пуле. Этот поток имеет свой собственный метод run(), который не может быть заменен вашим методом Runnable run(). Метод run() потока вызовет метод run() вашего Runnable, а затем, после того, как ваш Runnable.run() завершится, поток, в конце концов (придется выполнить некоторые расчеты), вернется к waiting. - person jbindel; 19.09.2010

Чтобы «запустить» поток более одного раза, создайте файл runnable. Например:

//NO
private class T extends Thread { //not necessary to implement runnable
    public void run(){
        //...
    }
}
void someMethod(){
    T a = new T();
    a.start();
    a.start(); //NO NO NO NO NO NO NO NO NO NO NO NO NO NO NO NO NO
}

Вместо,

//Yes
private class T implements Runnable {
    public void run(){
        //...
    }
}
void someMethod(){
    T a = new T();
    new Thread(a).start();
    new Thread(a).start(); //YES YES YES
}

Это также возможно сделать:

void someMethod(){
    final Runnable r = new Runnable(){
        public void run(){
            //...
        }
    };
    new Thread(r).start();
    new Thread(r).start();
}
// r could also be a field of you class. 
person Leo Izen    schedule 19.09.2010