Почему при повторном вызове Thread.start возникает исключение IllegalThreadStateException

#java #multithreading

#java #многопоточность

Вопрос:

 public class SieveGenerator{

static int N = 50;
public static void main(String args[]){

    int cores = Runtime.getRuntime().availableProcessors();

    int f[] = new int[N];

    //fill array with 0,1,2...f.length
    for(int j=0;j<f.length;j  ){
        f[j]=j;
    }

    f[0]=0;f[1]=0;//eliminate these cases

    int p=2;

    removeNonPrime []t = new removeNonPrime[cores];

    for(int i = 0; i < cores; i  ){
        t[i] = new removeNonPrime(f,p);
    }

    while(p <= (int)(Math.sqrt(N))){
        t[p%cores].start();//problem here because you cannot start a thread which has already started(IllegalThreadStateException)
        try{
            t[p%cores].join();
        }catch(Exception e){}
        //get the next prime
        p  ;
        while(p<=(int)(Math.sqrt(N))amp;amp;f[p]==0)p  ;
    }


    //count primes
    int total = 0;
    System.out.println();

    for(int j=0; j<f.length;j  ){
        if(f[j]!=0){
            total  ;
        }
    }
    System.out.printf("Number of primes up to %d = %d",f.length,total);
}
}


class removeNonPrime extends Thread{
int k;
int arr[];

public removeNonPrime(int arr[], int k){
    this.arr = arr;
    this.k = k;
}

public void run(){
    int j = k*k;
    while(j<arr.length){
        if(arr[j]%k == 0)arr[j]=0;
        j=j arr[k];

    }
}
}
  

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

Ответ №1:

как я мог бы каждый раз прерывать поток, чтобы обойти эту проблему?

Ответ в том, что вы не можете. После запуска a Thread может не быть перезапущен. Это четко задокументировано в javadoc для Thread . Вместо этого то, что вы действительно хотите сделать, это new создавать экземпляр RemoveNonPrime каждый раз, когда вы заходите в свой цикл.

У вас есть несколько других проблем в вашем коде. Во-первых, вам нужно увеличить значение p , прежде чем использовать его снова:

 for(int i = 0; i < cores; i  ){
    t[i] = new removeNonPrime(f,p); //<--- BUG, always using p=2 means only multiples of 2 are cleared
}
  

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

 while(p <= (int)(Math.sqrt(N))){
    t[p%cores].start();//
    try{
        t[p%cores].join(); //<--- BUG, only the thread which was just started can be running now
    }catch(Exception e){}
    //get the next prime
    p  ;
    while(p<=(int)(Math.sqrt(N))amp;amp;f[p]==0)p  ;
}
  

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

Вот подход, использующий ExecutorService, есть некоторые пробелы (…), которые вам нужно будет заполнить:

 /* A queue to trick the executor into blocking until a Thread is available when offer is called */
public class SpecialSyncQueue<E> extends SynchronousQueue<E> {
    @Override
    public boolean offer(E e) {
        try {
            put(e);
            return true;
        } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
            return false;
        }
    }
}

ExecutorService executor = new ThreadPoolExecutor(cores, cores, new SpecialSyncQueue(), ...);
void pruneNonPrimes() {
    //...
    while(p <= (int)(Math.sqrt(N))) {
        executor.execute(new RemoveNonPrime(f, p));
        //get the next prime
        p  ;
        while(p<=(int)(Math.sqrt(N))amp;amp;f[p]==0)p  ;
    }


    //count primes
    int total = 0;
    System.out.println();

    for(int j=0; j<f.length;j  ){
        if(f[j]!=0){
            total  ;
        }
    }
    System.out.printf("Number of primes up to %d = %d",f.length,total);
}



class RemoveNonPrime extends Runnable {
    int k;
    int arr[];

    public RemoveNonPrime(int arr[], int k){
        this.arr = arr;
        this.k = k;
    }

    public void run(){
        int j = k*k;
        while(j<arr.length){
            if(arr[j]%k == 0)arr[j]=0;
            j =k;
        }
    }
}
  

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

1. Я надеюсь, что о каждом экземпляре потока позаботится сборка мусора или что-то еще, что очищает временные созданные объекты. Или поток и созданные им объекты «умирают» сами по себе… У кошек 9 жизней, а у потоков только 1?

2. После запуска a Thread выполняет свой run метод. При run возврате выполнение потока завершается и ресурсы освобождаются. Любой объект, который был доступен только через этот поток, в конечном итоге будет помечен для сборки мусора. Но да, у Thread a есть только одно время жизни. ExecutorService позволяет обойти это, предоставляя Thread больше задач как Runnable через BlockingQueue .

Ответ №2:

Вместо этого вы могли бы реализовать Runnable и использовать новый поток ( $ Runnable здесь ).start() или использовать ExecutorService для повторного использования потоков.

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

1. Не приведет ли использование Runnable к той же проблеме или при запуске потока он автоматически остановится по завершении?

2. Нет; с помощью new Thread (Runnable).start() создается и запускается новый поток. Выполняемый объект может использоваться более одного раза. Да, это остановилось бы, если бы было завершено. Использование службы исполнителя, вероятно, было бы лучше, если бы вы создали действительно много потоков.

Ответ №3:

 * It is never legal to start a thread more than once.
* In particular, a thread may not be restarted once it has completed
* execution.
*
* @exception IllegalThreadStateException  if the thread was already started
*/
public synchronized void start() {
  

В Android в документе все еще упоминается, что мы получим IllegalThreadStateException if the thread was already started .
Однако для некоторых устройств это исключение не выдается (протестировано на Kyocera 7.0). На некоторых популярных устройствах, таких как Samsung, HTC, оно обычно выдает исключение

Я отвечаю здесь, потому что вопрос Android помечен как дублирующий этот вопрос.

Ответ №4:

Почему при повторном вызове Thread.start возникает исключение IllegalThreadStateException

Потому что разработчики JDK / JVM таким образом закодировали Thread.start() метод. Разумно функционально ожидать, что поток сможет перезапустить поток после завершения его выполнения, и это то, что предлагается в ответе крисбунни (и я вставил комментарий в этот ответ), но если вы посмотрите на Thread.start() реализацию, самая первая строка ,

 if (threadStatus != 0)
            throw new IllegalThreadStateException();
  

где threadStatus == 0 означает NEW состояние, поэтому я предполагаю, что реализация не сбрасывает это состояние на ноль после завершения выполнения, и поток остается в TERMINATED состоянии (ненулевом состоянии). Итак, когда вы создаете новый Thread экземпляр на том же Runnable сервере, вы фактически сбрасываете это состояние на ноль.

Кроме того, я заметил использование word — может и никогда в том же абзаце, поскольку Phan Van Linh указывает на различное поведение в некоторых операционных системах,

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

Я предполагаю, что они пытаются сказать в приведенном выше Javadoc, что даже если вы не получите IllegalThreadStateException на определенной ОС, это не является законным способом Java / Thread class amp; вы можете получить неожиданное поведение.

Знаменитые диаграммы состояний потоков изображают тот же сценарий — нет возврата из мертвого состояния в новое.

введите описание изображения здесь

Ответ №5:

Для доставки задач можно использовать ThreadPools с заданным количеством потоков. При инициализации вы устанавливаете количество потоков. Затем вы добавляете задачи для пула. И после вы можете блокировать, пока все задачи не завершат обработку. Вот некоторый пример кода.

Ответ №6:

Я совсем не уверен, что понимаю вопрос. Все методы остановки потоков, которые выполняются из других потоков, устарели; способ остановить поток — заставить его проверять переменную, к которой он и другой поток могут получить доступ (возможно, к изменчивой переменной), и периодически проверять ее запущенным потоком, чтобы убедиться, что он должен завершаться самостоятельно.

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

Ответ №7:

Метод Thread.IsAlive() может сообщить вам, был ли поток уже запущен. Просто сделайте это там, где вы хотите запустить свой поток:

    if(!t[p%cores].isAlive()){
       t[p%cores].start();
   }
  

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

1. Javadoc из ответа Фана Ван Линя ясно говорит — поток не может быть перезапущен после завершения выполнения . Меня смущает только использование слова может . Не уверен, разрешено ли это в определенных ситуациях и не разрешено ли в определенной ситуации. Если вы попытаетесь запустить поток после if(!thread.isAlive()) .. это приведет к тому, IllegalThreadStateException что он был запущен раньше.

2. Действительно, в его ответе это сказано. Пожалуйста, обратите внимание, что мой ответ примерно на 6,5 лет старше, чем его, и примерно на 8,5 лет старше на момент этого комментария, поэтому я не знаю, относится ли мой ответ к той же версии java, что и его, или мой ответ все еще актуален (я некоторое время не писал Java). Я также не знаю, что должно происходить с историческими ответами, подобными этому, когда мир движется вокруг них

3. Что касается вашего вопроса о «не может», я прочитал это как «не разрешено». Например, Вопрос: «могу ли я перезапустить поток после его запуска?», Ответ: «Нет, вы не можете»

4. Спасибо за ваш вклад, и я понимаю версию и историческую дилемму. Я проверил документацию Java 1.1.8 и упомянул ее там для start метода — Throws: исключение IllegalThreadStateException, если поток уже был запущен. Сигнатура метода и Javadoc немного отличаются от современных, но функциональность при перезапуске потока кажется постоянной. Я думаю, они просто добавили эту строку в документ для большей ясности — Никогда не разрешается запускать поток более одного раза. В частности, поток не может быть перезапущен после завершения выполнения. .