Introducing Threads
* thread
* process
* single-threaded process
* multithreaded process
* shared environment
* task
* A thread can complete multiple independent tasks but only one task at a time.
* shared memory,
thread is the smallest unit of execution that can be scheduled by the operating system.process is a group of associated threads that execute in the same shared environment.task is a single unit of work performed by a thread.shared environment, we mean that the threads in the same process share the same memory space and can communicate directly with one another.shared memory, we are generally referring to static variables as well as instance and local variables passed to a thread.Understanding Thread Concurrency
How does the system decide what to execute when there are more threads available than CPUs?
Operating systems use a thread scheduler to determine which threads should be currently executing, as shown in Figure 13.1. For example, a thread scheduler may employ a round-robin schedule in which each available thread receives an equal number of CPU cycles with which to execute, with threads visited in a circular order.
context switch
A context switch is the process of storing a thread’s current state and later restoring the state of the thread to continue execution.
thread priority
A thread priority is a numeric value associated with a thread that is taken into consideration by the thread scheduler when determining which threads should currently be executing. In Java, thread priorities are specified as integer values.
Runnable
@FunctionalInterface public interface Runnable {
void run();
}Runnable instance.Creating a Thread
new Thread(() -> System.out.print("Hello")).start();
System.out.print("World");Runnable object or lambda expression to the Thread constructor.start() start new threadRemember that order of thread execution is not often guaranteed. The exam commonly presents questions in which multiple tasks are started at the same time, and you must determine the result.
Calling run() Instead of start()
System.out.println("begin");
new Thread(printInventory).run();
new Thread(printRecords).run();
new Thread(printInventory).run();
System.out.println("end");Calling run() on a Thread or a Runnable does not start a new thread.
we can create a Thread and its associated task one of two ways in Java:
More generally, we can create a Thread and its associated task one of two ways in Java:
Runnable object or lambda expression to the Thread constructor.extends Thread and overrides the run() method.Creating a class that extends Thread is relatively uncommon and should only be done under certain circumstances, such as if you need to overwrite other thread methods.
Distinguishing Thread Types
* System thread
* User-defined thread
* Daemon thread
System thread, created by the(JVM) and runs in the background of the application. For example, garbage collection is managed by a system thread created by the JVM.User-defined thread, created by the application developer to accomplish a specific task.Daemon threadsdaemon threads.user-defined threads are not daemonsthread as daemon, before start()job.setDaemon(true);Managing a Thread’s Life Cycle
create thread -> NEW - start() -> RUNNABLE - run() completes-> TERMINATED
RUNNABLE -resource requested -> BLOCKED
RUNNABLE <-resource granted- BLOCKED
RUNNABLE -wait() -> WAITING
RUNNABLE <-notify() - WAITING
RUNNABLE -sleep() -> TIMED_WAITING
RUNNABLE <-time elapsed - TIMED_WAITING
Polling with Sleep
Thread.sleep(1_000); // 1 SECOND
Even though multithreaded programming allows you to execute multiple tasks at the same time, one thread often needs to wait for the results of another thread to proceed. One solution is to use polling. Polling is the process of intermittently checking data at some fixed interval.
We can improve this result by using the Thread.sleep() method to implement polling and sleep for 1,000 milliseconds, aka 1 second:
public class CheckResultsWithSleep {
private static int counter = 0;
public static void main(String[] a) {
new Thread(() -> {
for(int i = 0; i < 1_000_000; i++) counter++;
}).start();
while(counter < 1_000_000) {
System.out.println("Not reached yet");
try {
Thread.sleep(1_000); // 1 SECOND
} catch (InterruptedException e) {
System.out.println("Interrupted!");
}
}
System.out.println("Reached: "+counter);
} }Polling
Polling is the process of intermittently checking data at some fixed interval.
Interrupting a Thread
mainThread.interrupt();
One way to improve this program is to allow the thread to interrupt the main() thread when it’s done:
public class CheckResultsWithSleepAndInterrupt {
private static int counter = 0;
public static void main(String[] a) {
final var mainThread = Thread.currentThread();
new Thread(() -> {
for(int i = 0; i < 1_000_000; i++) counter++;
mainThread.interrupt();
}).start();
while(counter < 1_000_000) {
System.out.println("Not reached yet");
try {
Thread.sleep(1_000); // 1 SECOND
} catch (InterruptedException e) {
System.out.println("Interrupted!");
}
}
System.out.println("Reached: "+counter);
} }final var mainThread = Thread.currentThread();
mainThread.interrupt();
Calling interrupt() on a thread in the TIMED_WAITING or WAITING state causes the main() thread to become RUNNABLE again, triggering an InterruptedException. The thread may also move to a BLOCKED state if it needs to reacquire resources when it wakes up.
> [!NOTE]
Calling interrupt() on a thread already in a RUNNABLE state doesn’t change the state.
In fact, it only changes the behavior if the thread is periodically checking the Thread.isInterrupted() value state.
interrupt()
Thread.isInterrupted()
Thread methods
Creating Threads with the Concurrency API
java.util.concurrent packageExecutorService interface, which defines services that create and manage threads.ExecutorService interface,Introducing the Single-Thread Executor
ExecutorService service = Executors.newSingleThreadExecutor();
try {
System.out.println("begin");
service.execute(printInventory);
service.execute(printRecords);
service.execute(printInventory);
System.out.println("end");
} finally {
service.shutdown();
}Possible output:
begin
Printing zoo inventory
Printing record: 0
Printing record: 1end
Printing record: 2
Printing zoo inventory
ExecutorService service = Executors.newSingleThreadExecutor();
Executors factory class that can be used to create instances of the ExecutorService interface.sequentially.end text is output while our thread executor tasks are still running.Shutting Down a Thread Executor
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedExceptionboolean isTerminated()boolean isShutdown()void shutdown()List<Runnable> shutdownNow()non-daemon thread on the first task that is executed, so failing to call shutdown() will result in your application never terminating.rejecting any new tasks submitted to the thread executorcontinuing to execute any previously submitted tasks.isShutdown() will return true, while isTerminated() will return false.RejectedExecutionException will be thrown.isShutdown() and isTerminated() will both return true.For the exam, you should be aware that shutdown() does not stop any tasks that have already been submitted to the thread executor.
What if you want to cancel all running and upcoming tasks?
shutdownNow(),List<Runnable> shutdownNow()
* Attempts to stop all actively executing tasks,
* halts the processing of waiting tasks,
* and returns a list of the tasks that were awaiting execution.
* This method does not wait for actively executing tasks to terminate.
* Use awaitTermination to do that.
* There are no guarantees beyond best-effort attempts to stop processing actively executing tasks.
* For example, typical implementations will cancel via Thread.interrupt(), so any task that fails to respond to interrupts may never terminate.
Submitting TasksExecutorService
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionExceptionFuture<?> submit(Runnable task)<T> Future<T> submit(Runnable task, T result)<T> Future<T> submit(Callable<T> task)Executor
void execute(Runnable command)You can submit tasks to an ExecutorService instance multiple ways.
1. void execute(Runnable command)
Executes Runnable task at some point in future.
2. Future<?> submit(Runnable task)
Executes Runnable task at some point in future and returns Future representing task.
3. <T> Future<T> submit(Callable<T> task)
Executes Callable task at some point in future and returns Future representing pending results of task.
4. <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
Executes given tasks and waits for all tasks to complete. Returns List of Future instances in same order in which they were in original collection.
5. <T> T invokeAny(Collection<? extends Callable<T>> tasks)
Executes given tasks and waits for at least one to complete.
Submitting Tasks: execute() vs. submit()
The submit() method advantage over execute() method is a return object that can be used to track the result.
For the exam, you need to be familiar with both execute() and submit(), but in your own code we recommend submit() over execute() whenever possible.
How do we know when a task submitted to an ExecutorService is complete?
submit() method returns a Future<V> instance that can be used to determine this result.
Future<?> future = service.submit(() -> System.out.println("Hello"));
Future type is actually an interface.Future instance is returned by various API methods.Future methods
boolean isDone() boolean isCancelled() boolean cancel(boolean mayInterruptIfRunning) V get() V get(long timeout, TimeUnit unit)