开发者

java thread reuse

开发者 https://www.devze.com 2022-12-21 14:47 出处:网络
I have always read that creating threads is expensive. I also know that you cannot rerun a thread. I see in the doc of Executors class:

I have always read that creating threads is expensive.

I also know that you cannot rerun a thread.

I see in the doc of Executors class:

Creates a thread pool that creates new threads as needed, but will reuse previously constructe开发者_运维技巧d threads when they are available.

Mind the word 'reuse'.

How do thread pools 'reuse' threads?


I think I understood what is confuzzabling you so here's my longer answer: the terminology is a tiny bit misleading (obviously, or you wouldn't ask that question specifically putting the emphasis on 'reuse'):

How do thread pools 'reuse' threads?

What is happening is that a single thread can be used to process several tasks (typically passed as Runnable, but this depend on your 'executor' framework: the default executors accepts Runnable, but you could write your own "executor" / thread-pool accepting something more complex than a Runnable [like, say, a CancellableRunnable]).

Now in the default ExecutorService implementation if a thread is somehow terminated while still in use, it is automatically replaced with a new thread, but this is not the 'reuse' they're talking about. There is no "reuse" in this case.

So it is true that you cannot call start() on a Java Thread twice but you can pass as many Runnable as you want to an executor and each Runnable's run() method shall be called once.

You can pass 30 Runnable to 5 Java Thread and each worker thread may be calling, for example, run() 6 times (practically there's not guarantee that you'll be executing exactly 6 Runnable per Thread but that is a detail).

In this example start() would have been called 6 times. Each one these 6 start() will call exactly once the run() method of each Thread:

From Thread.start() Javadoc:

 * Causes this thread to begin execution; the Java Virtual Machine 
 * calls the <code>run</code> method of this thread.

BUT then inside each Thread's run() method Runnable shall be dequeued and the run() method of each Runnable is going to be called. So each thread can process several Runnable. That's what they refer to by "thread reuse".

One way to do your own thread pool is to use a blocking queue on to which you enqueue runnables and have each of your thread, once it's done processing the run() method of a Runnable, dequeue the next Runnable (or block) and run its run() method, then rinse and repeat.

I guess part of the confusion (and it is a bit confusing) comes from the fact that a Thread takes a Runnable and upon calling start() the Runnable 's run() method is called while the default thread pools also take Runnable.


The run method of threads in a thread pool does not consist only of running a single task. The run method of a thread in a thread pool contains a loop. It pulls a task off of a queue, executes the task (which returns back to the loop when it is complete), and then gets the next task. The run method doesn't complete until the thread is no longer needed.

Edited to add:

Here is the run method of the Worker inner class in ThreadPoolExecutor.

696:         /**
697:          * Main run loop
698:          */
699:         public void run() {
700:             try {
701:                 Runnable task = firstTask;
702:                 firstTask = null;
703:                 while (task != null || (task = getTask()) != null) {
704:                     runTask(task);
705:                     task = null; // unnecessary but can help GC
706:                 }
707:             } finally {
708:                 workerDone(this);
709:             }
710:         }


The thread pool consists of a number of fixed worker threads that can take tasks from an internal task queue. So if one task ends, the thread does not end but waits for the next task. If you abort a thread, it is automatically replaced.

Look at the documentation for more details.


A thread pool creates its own threads and supplies its own clever little Runnables for those threads. Those Runnables never end but synchronize on a queue (they wait()) until a Callable is present in that queue; they are notified when that happens and their Runnable runs the Callable from the queue and the entire scenario repeats itself again.


My attempts to understand how "Thread reuse" really work led me to your question. So I hope this code snippet will help you as much as it helped me while writing it.

Side note. I really liked SyntaxT3rr0r's answer and I hope the code below gives the visual representation.

So it is true that you cannot call start() on a Java Thread twice but you can pass as many Runnable as you want to an executor and each Runnable's run() method shall be called once.

class ThreadPool {
    Queue<Runnable> tasks = new LinkedList<>();

    public ThreadPool() {
        Thread worker1 = new Thread(() -> {
            while (true) {
                Runnable newTaskToBeExecuted = pollNextTask();
                if (newTaskToBeExecuted != null) newTaskToBeExecuted.run();
            }
        }, "Worker 1");
        worker1.start();

        Thread worker2 = new Thread(() -> {
            while (true) {
                Runnable newTaskToBeExecuted = pollNextTask();
                if (newTaskToBeExecuted != null) newTaskToBeExecuted.run();
            }
        }, "Worker 2");
        worker2.start();

    }

    public void execute(Runnable newTask) {
        tasks.add(newTask);
    }

    private synchronized Runnable pollNextTask() {
        return tasks.poll();
    }
}

This Thread pool implementation has two workers (Threads) both with an endless loop polling Runnables from a queue and executing their run() methods.

The "reuse" here is that both workers never stop polling meaning the thread always has work to do and never dies even if there are no tasks. When a task in a worker finishes it takes the next one in the queue. If there are none left the loop keeps spinning(could be improved to save resources but still not destroy the thread)

Using the ThreadPool would look like this

public static void main(String[] args) {
    ThreadPool myThreadPool = new ThreadPool();

    Runnable task1 = () -> {
        System.out.println("task1 running in thread: " + currentThread().getName());
        try {
            System.out.println("task1 start at " + new Date().getTime());
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("task1 end at " + new Date().getTime());
    };

    Runnable task2 = () -> {
        System.out.println("task2 running in thread: " + currentThread().getName());
        try {
            System.out.println("task2 start at " + new Date().getTime());
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("task2 end at " + new Date().getTime());
    };

    Runnable task3 = () -> {
        System.out.println("task3 running in thread: " + currentThread().getName());
        try {
            System.out.println("task3 start at " + new Date().getTime());
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("task3 end at " + new Date().getTime());
    };

    Runnable task4 = () -> {
        System.out.println("task4 running in thread: " + currentThread().getName());
        try {
            System.out.println("task4 start at " + new Date().getTime());
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("task4 end at " + new Date().getTime());
    };

    myThreadPool.execute(task1);
    myThreadPool.execute(task2);
    myThreadPool.execute(task3);
    myThreadPool.execute(task4);
}

With output

task2 running in thread: Worker 2
task1 running in thread: Worker 1
task2 start at 1659719164057
task1 start at 1659719164053
task2 end at 1659719164172
task3 running in thread: Worker 2
task3 start at 1659719164173
task3 end at 1659719164374
task4 running in thread: Worker 2
task4 start at 1659719164377
task4 end at 1659719164682
task1 end at 1659719165077

You can see that when all threads are busy no tasks are started. The moment a thread is done with a task the next in the queue is picked.

Here you can find the the code written a little bit better so anyone can run it and play with the thread pool size and number of tasks.

I'm in the begging of my Multithreading adventure so any improvements and comments to the code or my approach are welcomed.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号