Create both platform and virtual threads. Use both Runnable and Callable objects, manage the thread lifecycle, and use different Executor services and concurrent API to run tasks.
Develop thread-safe code, using locking mechanisms and concurrent API.
Process Java collections concurrently and utilize parallel streams.
1. The correct answer is C.
Explanation:
Thread thread = Thread.ofVirtual(); thread.start(task);
Thread.ofVirtual()
returns a Thread.Builder
, not a Thread
. The start()
method on Thread.Builder
takes a Runnable
, but this syntax is incorrect.Thread thread = Thread.ofVirtual().unstarted(task).run();
run()
directly does not start a new thread. It executes the task in the current thread.Thread thread = Thread.ofVirtual().start(task);
Thread thread = Thread.ofVirtual(); task.run();
Thread thread = Thread.start(task);
Thread.start(task)
is not a valid static method in Java 21.2. The correct answer is B.
Explanation:
synchronized (this) { counter++; }
this
cannot be used in a static context. In the main
method, this
is not available. For a static field like counter
, you need to synchronize on a static object or class.synchronized (Main.class) { counter++; }
Main.class
ensures that only one thread can enter the synchronized block at a time for all instances of Main
, which is appropriate for protecting static fields like counter
.synchronized (task) { counter++; }
task
is a Runnable
object, and synchronizing on it does not effectively control access to the shared static field counter
.synchronized (counter) { counter++; }
counter
is a primitive type (int
), and you cannot synchronize on a primitive type. Synchronization requires an object.synchronized (System.out) { counter++; }
System.out
is not related to controlling access to counter
. It would also interfere with other potential uses of System.out
.3. The correct answers are B and C.
Explanation:
AtomicInteger
is part of the java.util.concurrent.atomic
package, but it does not provide atomic operations for increment and decrement.
AtomicInteger
provides atomic operations for increment and decrement, such as incrementAndGet()
and decrementAndGet()
.AtomicReference
can only be used with reference types, not primitive types.
AtomicReference
is designed to work with reference types and cannot be used with primitive types directly.AtomicLong
supports atomic operations on long
values, including getAndIncrement()
and compareAndSet()
methods.
AtomicLong
provides atomic operations on long
values, including getAndIncrement()
and compareAndSet()
methods.AtomicBoolean
can be used to perform atomic arithmetic operations on boolean
values.
AtomicBoolean
is used for atomic updates to boolean
values, but it does not support atomic arithmetic operations.4. The correct answer is A.
Explanation:
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
finally
block, which is the proper use of the Lock
interface.lock.lock();
count++;
lock.unlock();
lock.lock()
and lock.unlock()
, the lock will not be released, potentially causing a deadlock.try {
lock.lock(() -> {
count++;
});
} finally {
lock.unlock();
}
lock.lock()
call.synchronized(lock) {
count++;
}
synchronized
block is used with the lock
object itself, which is not the correct usage of the Lock
interface and does not provide the intended functionality.5. The correct answer is A.
Explanation:
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> System.out.println("Task executed"));
}
try-with-resources
block with the new Executors.newVirtualThreadPerTaskExecutor()
method introduced in Java 21. The ExecutorService
will be automatically closed when the try
block exits, eliminating the need for explicit shutdown calls.try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> System.out.println("Task executed"));
} finally {
executor.shutdown();
}
shutdown()
in the finally block. With try-with-resources
, the ExecutorService
is automatically closed, making the explicit shutdown()
call redundant and potentially harmful.ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
try {
executor.submit(() -> System.out.println("Task executed"));
} finally {
executor.close();
}
the try-with-resources
syntax. While it does correctly close the ExecutorService
, it doesn’t take advantage of the automatic resource management provided by try-with-resources
.try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> System.out.println("Task executed"));
executor.awaitTermination(1, TimeUnit.SECONDS);
}
awaitTermination()
. In a try-with-resources
block, the ExecutorService
is automatically closed when the block exits, making the explicit wait for termination unnecessary and potentially causing the thread to block for 1 second.6. The correct answer is D.
Explanation:
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
Future<Integer> future = executor.submit(task);
System.out.println(future.get());
}
InterruptedException
and ExecutionException
that future.get()
can throw.try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
Future<Integer> future = executor.submit(task);
Integer result = future.get(1, TimeUnit.SECONDS);
System.out.println(result);
}
InterruptedException
, ExecutionException
, and TimeoutException
) that future.get(long, TimeUnit)
can throw.try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
Future<Integer> future = executor.submit(task);
executor.shutdown();
System.out.println(future.get());
}
executor.shutdown()
. In a try-with-resources
block, the ExecutorService
is automatically closed when the block exits. Also, it doesn’t handle the potential exceptions from future.get()
.try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
Future<Integer> future = executor.submit(task);
try {
Integer result = future.get();
System.out.println(result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
try-with-resources
to automatically close the ExecutorService
, properly submits the Callable
task, retrieves the result using Future.get()
, and handles the potential InterruptedException
and ExecutionException
that might be thrown.7. The correct answer is A.
Explanation:
ConcurrentHashMap
allows concurrent read and write operations, and retrieval operations do not block even when updates are being made.
ConcurrentHashMap
is designed to handle concurrent access, allowing multiple threads to read and write simultaneously without blocking read operations during updates.CopyOnWriteArrayList
is optimized for scenarios with a high number of write operations compared to read operations.
CopyOnWriteArrayList
is optimized for scenarios where read operations are far more frequent than write operations because it creates a new copy of the array on each write, which can be costly if writes are frequent.ConcurrentSkipListSet
does not kept elements sorted.
ConcurrentSkipListSet
keep elements according to their natural ordering, or by a Comparator
provided at set creation time.BlockingQueue
implementations like LinkedBlockingQueue
allow elements to be added and removed concurrently without any internal locking mechanisms.
BlockingQueue
implementations like LinkedBlockingQueue
do use internal locking mechanisms to handle concurrent access safely.8. The correct answer is B.
Explanation:
9. The correct answer is B.
Explanation:
int sum = numbers.parallelStream().reduce(1, Integer::sum);
System.out.println(sum);
1
as the identity value. The identity value for sum should be 0
, as it is the neutral element for addition. Starting the reduction with 1
will result in an incorrect sum that is incremented by 1
.int sum = numbers.parallelStream().reduce(0, Integer::sum).collect();
System.out.println(sum);
parallelStream()
to create a parallel stream and the reduce
method with the identity value 0
and the method reference Integer::sum
to sum the elements.int sum = numbers.stream().reduce(0, Integer::sum);
System.out.println(sum);
stream()
) instead of a parallel stream. While it correctly sums the elements, it does not demonstrate the use of a parallel stream as specified in the question.int sum = numbers.parallelStream().collect(reduce(0, Integer::sum));
System.out.println(sum);
collect()
method in combination with reduce()
, which is not the correct syntax. The collect()
method is used for mutable reduction and is typically used with collectors, not with the reduce()
operation directly.Do you like what you read? Would you consider?
Do you have a problem or something to say?