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?