Java Reference
In-Depth Information
Lines 21-23 each invoke the
ExecutorService
's
execute
method, which executes its
Runnable
argument (in this case a
PrintTask
) some time in the future. The specified task
may execute in one of the threads in the
ExecutorService
's thread pool, in a new thread
created to execute it, or in the thread that called the
execute
method—the
ExecutorSer-
vice
manages these details. Method
execute
returns immediately from each invocation—
the program does
not
wait for each
PrintTask
to finish. Line 26 calls
ExecutorService
method
shutdown
, which notifies the
ExecutorService
to
stop accepting new tasks, but con-
tinues executing tasks that have already been submitted
. Once all of the previously submitted
Runnable
s have completed, the
ExecutorService
terminates. Line 28 outputs a message
indicating that the tasks were started and the
main
thread is finishing its execution.
Main Thread
The code in
main
executes in the
main thread
, which is created by the JVM. The code in
the
run
method of
PrintTask
(lines 21-37 of Fig. 23.3) executes whenever the
Executor
starts each
PrintTask
—again, this is sometime after they're passed to the
ExecutorSer-
vice
's
execute
method (Fig. 23.4, lines 21-23). When
main
terminates, the program it-
self continues running because there are still tasks that must finish executing. The program
will not terminate until these tasks complete.
Sample Outputs
The sample outputs show each task's name and sleep time as the thread goes to sleep. The
thread with the shortest sleep time
in most cases
awakens first, indicates that it's done sleep-
ing and terminates. In Section 23.8, we discuss multithreading issues that could prevent
the thread with the shortest sleep time from awakening first. In the first output, the
main
thread terminates
before
any of the
PrintTask
s output their names and sleep times. This
shows that the
main
thread runs to completion before any of the
PrintTask
s gets a chance
to run. In the second output, all of the
PrintTask
s output their names and sleep times
before
the
main
thread terminates. This shows that the
PrintTask
s started executing before
the main thread terminated. Also, notice in the second example output,
task3
goes to
sleep before
task2
last, even though we passed
task2
to the
ExecutorService
's
execute
method before
task3
. This illustrates the fact that
we cannot predict the order in which the
tasks will start executing, even if we know the order in which they were created and started
.
Waiting for Previously Scheduled Tasks to Terminate
After scheduling tasks to execute, you'll typically want to
wait for the tasks to complete
—for
example, so that you can use the tasks' results. After calling method
shutdown
, you can call
ExecutorService
method
awaitTermination
to wait for scheduled tasks to complete. We
demonstrate this in Fig. 23.7. We purposely did not call
awaitTermination
in Fig. 23.4
to demonstrate that a program can continue executing after the main thread terminates.
When multiple threads share an object and it's
modified
by one or more of them, indeter-
minate results may occur (as we'll see in the examples) unless access to the shared object is
managed properly. If one thread is in the process of updating a shared object and another
thread also tries to update it, it's uncertain which thread's update takes effect. Similarly, if
one thread is in the process of updating a shared object and another thread tries to read it,
it's uncertain whether the reading thread will see the old value or the new one. In such