Chapter 13 Concurrency Flashcards

1
Q

Introducing Threads

A
  • A thread is the smallest unit of execution that can be scheduled by the operating system.
  • A process is a group of associated threads that execute in the same shared environment.
  • It follows, then, that a single-threaded process is one that contains exactly one thread,
  • whereas a multithreaded process supports more than one thread.
  • By shared environment, we mean that the threads in the same process share the same memory space and can communicate directly with one another.
  • A task is a single unit of work performed by a thread.
  • A thread can complete multiple independent tasks but only one task at a time.
  • By shared memory, we are generally referring to static variables as well as instance and local variables passed to a thread.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

Understanding Thread Concurrency

A
  • The property of executing multiple threads and processes at the same time is referred to as concurrency.
  • Operating systems use a thread scheduler to determine which threads should be currently executing
  • A context switch is the process of storing a thread’s current state and later restoring the state of the thread to continue execution.
  • Finally, a thread can interrupt or supersede another thread if it has a higher thread priority than the other thread. 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.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Creating a Thread

A

One of the most common ways to define a task for a thread is by using the Runnable instance.
Runnable is a functional interface that takes no arguments and returns no data.

@FunctionalInterface public interface Runnable {
void run();
}

With this, it’s easy to create and start a thread. In fact, you can do so in one line of code using the Thread class:

new Thread(() -> System.out.print("Hello")).start();
System.out.print("World");

More generally, we can create a Thread and its associated task one of two ways in Java:
* Provide a Runnable object or lambda expression to the Thread constructor.
* Create a class that 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.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

Calling run() Instead of start()

A

Calling run() on a Thread or a Runnable does not start a new thread.

System.out.println("begin");
new Thread(printInventory).run();
new Thread(printRecords).run();
new Thread(printInventory).run();
System.out.println("end");
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Distinguishing Thread Types

A

A system thread is created by the Java Virtual Machine (JVM) and runs in the background of the application. For example, garbage collection is managed by a system thread created by the JVM.

Alternatively, a user-defined thread is one created by the application developer to accomplish a specific task.

System and user-defined threads can both be created as daemon threads. A daemon thread is one that will not prevent the JVM from exiting when the program finishes. A Java application terminates when the only threads that are running are daemon threads. For example, if garbage collection is the only thread left running, the JVM will automatically shut down.

by default, user-defined threads are not daemons, and the
program will wait for them to finish.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Managing a Thread’s Life Cycle

A

You can query a thread’s state by calling getState() on the thread object.

  1. Every thread is initialized with a NEW state.
  2. As soon as start() is called, the thread is moved to a RUNNABLE state. Does that mean it is actually running? Not exactly: it may be running, or it may not be. The RUNNABLE state just means the thread is able to be run.
  3. Once the work for the thread is completed or an uncaught exception is thrown, the thread state becomes TERMINATED, and no more work is performed.
  4. While in a RUNNABLE state, the thread may transition to one of three states where it pauses its work: BLOCKED, WAITING, or TIMED_WAITING. This figure includes common transitions between thread states, but there are other possibilities. For example, a thread in a WAITING state might be triggered by notifyAll(). Likewise, a thread that is interrupted by another thread will exit TIMED_WAITING and go straight back into RUNNABLE.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Polling with Sleep

A

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);
} }
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Interrupting a Thread

A

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);
} }
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

> [!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.

A
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

Creating Threads with the Concurrency API

A
  • java.util.concurrent package
  • The Concurrency API includes the ExecutorService interface, which defines services that create and manage threads.
  • You first obtain an instance of an ExecutorService interface, and then you send the service tasks to be processed.
  • The framework includes numerous useful features, such as thread pooling and scheduling.
  • It is recommended that you use this framework any time you need to create and execute a separate task, even if you need only a single thread.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

Introducing the Single-Thread Executor

A
  • ExecutorService is an interface
  • The Concurrency API includes the Executors factory class that can be used to create instances of the ExecutorService object.
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: 1
end
Printing record: 2
Printing zoo inventory
  • we use the newSingleThreadExecutor() method to create the service.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

Shutting Down a Thread Executor

A
  • Once you have finished using a thread executor, it is important that you call the shutdown() method.
  • A thread executor creates a non-daemon thread on the first task that is executed, so failing to call shutdown() will result in your application never terminating.
  • The shutdown process for a thread executor involves first rejecting any new tasks submitted to the thread executor while continuing to execute any previously submitted tasks.
  • During this time, calling isShutdown() will return true, while isTerminated() will return false.
  • If a new task is submitted to the thread executor while it is shutting down, a RejectedExecutionException will be thrown.
  • Once all active tasks have been completed, isShutdown() and isTerminated() will both return true. Figure 13.3 shows the life cycle of an ExecutorService object.

For the exam, you should be aware that shutdown() does not stop any tasks that have already been submitted to the thread executor.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

What if you want to cancel all running and upcoming tasks?

A

The ExecutorService provides a method called shutdownNow(), which attempts to stop all running tasks and discards any that have not been started yet. It is not guaranteed to succeed because it is possible to create a thread that will never terminate, so any attempt to interrupt it may be ignored.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

Submitting Tasks

A

You can submit tasks to an ExecutorService instance multiple ways.
1. The first method we presented, execute(), is inherited from the Executor interface, which the ExecutorService interface extends. The execute() method takes a Runnable instance and completes the task asynchronously. Because the return type of the method is void, it does not tell us anything about the result of the task. It is considered a “fire-and-forget” method, as once it is submitted, the results are not directly available to the calling thread.
2. Fortunately, the writers of Java added submit() methods to the ExecutorService interface, which, like execute(), can be used to complete tasks asynchronously. Unlike execute(), though, submit() returns a Future instance that can be used to determine whether the task is complete. It can also be used to return a generic result object after the task has been completed.

  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.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q
A
How well did you know this?
1
Not at all
2
3
4
5
Perfectly