Как задать имя потока, класс которого имплементирует интерфейс Runnable в Java?

Имеется класс Task, реализующий интерфейс Runnable, который (класс) в будущем станет потоком. Как задать id для этого класса, чтобы использовать в дальшейнем.
Вот, что я использую. Создаю класс Task с конструктором, который задает id, и переопределяю метод public String toString():

public class Task implements Runnable {
    private final int id;

    static Blocker blocker = new Blocker();

    public Task(int id) {
        this.id = id;
    }

    @Override
    public void run() {
        blocker.waitingCall();
    }

    @Override
    public String toString() {
        return "Thread (" + id + ")";
    }
}

Поток создаю с помощью ExecutorService:

ExecutorService exec = Executors.newCachedThreadPool();

for(int i = 0; i < 5; i++) {
    exec.execute(new Task(i));
}

Далее хочу в другом классе в синхронизированном методе вывести id потока, который будет находится в данном методе с помощью Thread.currentThread() вот так:

synchronized void waitingCall() {
        try {
            while (!Thread.interrupted()) {
                wait();
                System.out.println(Thread.currentThread() + " from class \"" + this.getClass().getSimpleName() + "\" waiting...Zzz");
            }
        } catch (InterruptedException e) {
            System.out.println("A permissible way out");
        }
    }

Но на выходе получаю вот такую строку: Thread[pool-1-thread-1,5,main] from class "Blocker" waiting...Zzz, а не ожидаемую Thread (id). Почему так происходит? Интересует именно способ задания имени для потока, который создается с помощью реализации интерфейса Runnable, а не через наследование класса Thread.


Ответы (2 шт):

Автор решения: Roman Konoval

Есть всего два способ задания имени:

  1. через конструктор класса Thread
  2. изменить имя уже созданного потока с помощью Thread.setName.

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

Вот это из вопроса неверно, когда используется пул потоков:

Имеется класс Task, реализующий интерфейс Runnable, который (класс) в будущем станет потоком.

Экземпляр Task не станет потоком, когда его запустите с помощью executor-а. Executor воспользуется своим потоком, который он создал, и тот существующий поток вызовет метод Task.run. После этого executor вызовет Task.run у следующего экземпляра опять же в существующем потоке. Грубо говоря поток executor-а исполняет цикл:

  Runnable task;
  while((task = queue.getNextTask() != null) {
    task.run();
  }

И потому не работает способ с использованием ThreadFactory.

Можно изменить имя текущего потока так на уровне задачи:

@Override
public void run() {
    String oldName = Thread.currentThread().getName();
    try {
       Thread.currentThread().setName(toString());
       blocker.waitingCall();
    } finally {
       Thread.currentThread().setName(oldName);
    }
}

Второй вариант - это создать свой executor, нужно наследоваться от ThreadPoolExecutor и переопределить в нем методы beforeExecute и afterExecute (если нужно восстановить имя):

class NamingThreadPoolExecutor extends ThreadPoolExecutor {
  protected void beforeExecute(Thread t, Runnable r) { 
     t.setName(r.toString());
  }
}

Потом использовать свой класс вместо Executors (точно так же как делается в Executors.newCachedThreadPool):

ExecutorService exec = new NamingThreadPoolExecutor(
        0, Integer.MAX_VALUE,
        60L, TimeUnit.SECONDS,
        new SynchronousQueue<Runnable>());

В этом случае сами задачи модифицировать не нужно.

→ Ссылка
Автор решения: Barmaley

Я бы посоветовал создавать Thread через ThreadFactory, тогда у вас будет возможность контролировать процесс создания нити и в этот момент задавать ему нужное имя, например:

class SimpleThreadFactory implements ThreadFactory {
   public Thread newThread(Runnable r) {
     return new Thread(r, r.toString());
   }
 }

 ExecutorService exec = Executors.newCachedThreadPool(new SimpleThreadFactory());
→ Ссылка