What are fail-fast and fail-safe iterators?
ConcurrentModificationException
if the underlying collection is modified structurally (elements are added or removed) by any means other than the iterator's own remove()
method while the iterator is traversing the collection. They make a best-effort attempt to detect concurrent modification but do not offer a guarantee. The iterators for most non-concurrent collections like ArrayList
and HashMap
are fail-fast.java.util.concurrent
package, like ConcurrentHashMap
and CopyOnWriteArrayList
, are fail-safe.How do you create a thread in Java?
There are two primary ways to create a thread in Java:
1. By Extending the `Thread` Class:
java.lang.Thread
class.run()
method in your class. This method contains the code that will be executed by the new thread.start()
method. The start()
method will in turn call the run()
method in the new thread of execution.class MyThread extends Thread {
public void run() {
System.out.println("Thread is running...");
}
}
public class Main {
public static void main(String args[]) {
MyThread t1 = new MyThread();
t1.start();
}
}
2. By Implementing the `Runnable` Interface:
java.lang.Runnable
interface.run()
method.Thread
object passing your runnable object to its constructor, and then call the thread's start()
method.class MyRunnable implements Runnable {
public void run() {
System.out.println("Thread is running...");
}
}
public class Main {
public static void main(String args[]) {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
t1.start();
}
}
Implementing `Runnable` is generally preferred because it allows your class to extend another class (as Java does not support multiple inheritance of classes) and it promotes a better separation of concerns (the task to be run is separate from the thread mechanism itself).
What is synchronization in Java?
Synchronization is a mechanism in Java that controls the access of multiple threads to any shared resource. It is a tool for preventing race conditions and ensuring data consistency in a multi-threaded environment.
When a thread wants to execute a synchronized block of code, it must first acquire the intrinsic lock (or 'monitor') of the object associated with that code. As long as the thread holds the lock, no other thread can enter any synchronized block on the same object. When the thread exits the synchronized block, it automatically releases the lock, allowing another waiting thread to acquire it.
Synchronization can be achieved in two ways:
synchronized
keyword to a method signature. This locks the entire method, using the `this` object's lock for instance methods, or the `Class` object's lock for static methods.public synchronized void incrementCounter() {
// ... code to modify shared state
}
synchronized
keyword on a block of code. This allows you to synchronize only the critical section of a method rather than the entire method, which can improve performance. It also allows you to specify which object to lock on.public void myMethod() {
// ... non-critical code
synchronized(this) {
// ... code to modify shared state
}
// ... other non-critical code
}
What is the difference between `sleep()` and `wait()`?
Both methods are used to pause the execution of a thread, but they are used for different purposes and behave differently.
Feature | sleep() | wait() |
---|---|---|
Class | It is a static method of the Thread class. | It is an instance method of the Object class. |
Lock Release | The thread does not release the lock (monitor) it holds while it is sleeping. | The thread releases the lock it holds so that another thread can acquire it and enter a synchronized block. |
Context | Can be called from any context. | Must be called from within a synchronized block or method, otherwise it throws an IllegalMonitorStateException . |
Waking Up | Wakes up automatically after the specified time has elapsed, or if it is interrupted. | Wakes up only when another thread calls notify() or notifyAll() on the same object, or if it is interrupted, or if it times out (if a timeout was specified). |
Purpose | Used to pause the current thread for a specified period without any specific condition. | Used for inter-thread communication and conditional waiting. A thread waits for a certain condition to become true. |
What is a deadlock?
A deadlock is a situation in a multi-threaded environment where two or more threads are blocked forever, each waiting for a resource that is held by another thread in the same set.
For a deadlock to occur, four conditions (known as the Coffman conditions) must be met simultaneously:
The most common way to prevent deadlock is to break the circular wait condition by ensuring that all threads acquire locks in a fixed, predetermined order.
What is the `volatile` keyword?
The volatile
keyword in Java is a field modifier that provides a guarantee of visibility and ordering for shared variables in a multi-threaded context.
volatile
, any write to that variable by one thread is guaranteed to be immediately visible to any other thread that reads the variable. It ensures that reads and writes happen directly from/to main memory, bypassing any thread-local caches where the value might otherwise become stale.However, volatile
does not guarantee atomicity. For example, an operation like count++
(which is actually three separate operations: read, increment, write) is not atomic and is not made thread-safe just by making `count` volatile. For atomic compound actions, you still need to use `synchronized` blocks or classes from the `java.util.concurrent.atomic` package (like `AtomicInteger`).
What are Lambda Expressions in Java 8?
A lambda expression is a short block of code which takes in parameters and returns a value. They are a key feature of Java 8, enabling a more functional style of programming.
Lambda expressions are essentially anonymous functions. They allow you to treat functionality as a method argument, or code as data. They are primarily used to provide an implementation for a functional interface (an interface with a single abstract method).
Syntax:
(parameter1, parameter2, ...) -> { code block }
Example (without Lambda):
List<String> names = Arrays.asList("peter", "anna", "mike");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.compareTo(b);
}
});
Example (with Lambda):
List<String> names = Arrays.asList("peter", "anna", "mike");
Collections.sort(names, (a, b) -> a.compareTo(b));
As you can see, lambdas make the code much more concise and readable.
What is the Stream API in Java 8?
The Stream API, introduced in Java 8, is used to process sequences of elements from a source, such as a collection. It provides a declarative, functional-style way to perform operations on data.
A stream is not a data structure itself; it takes input from Collections, Arrays, or I/O channels. Stream operations are divided into two categories:
filter(predicate)
: Filters elements based on a condition.map(function)
: Transforms each element into another object.sorted()
: Sorts the stream.forEach(action)
: Performs an action for each element.collect(collector)
: Transforms the stream into a different data structure, like a `List` or `Set`.reduce(identity, accumulator)
: Reduces the stream to a single summary value.Example:
List<String> names = Arrays.asList("apple", "banana", "apricot", "cherry");
List<String> filteredAndUppercased = names.stream() // Create a stream
.filter(s -> s.startsWith("a")) // Intermediate operation
.map(String::toUpperCase) // Intermediate operation
.sorted() // Intermediate operation
.collect(Collectors.toList()); // Terminal operation
// Result: ["APPLE", "APRICOT"]
What is the `Optional` class in Java 8?
The Optional
class, introduced in Java 8, is a container object which may or may not contain a non-null value. Its primary purpose is to provide a better way to handle `null` values and avoid `NullPointerException`.
Instead of a method returning `null` when a value is not present, it can return an empty `Optional`. This forces the caller to explicitly handle the case where a value is absent, leading to more robust code.
Key Methods:
Optional.of(value)
: Creates an Optional. Throws `NullPointerException` if the value is null.Optional.ofNullable(value)
: Creates an Optional. Allows the value to be null.Optional.empty()
: Creates an empty Optional.isPresent()
: Returns `true` if a value is present, otherwise `false`.get()
: Returns the value if present, otherwise throws `NoSuchElementException`.orElse(defaultValue)
: Returns the value if present, otherwise returns a default value.orElseThrow(exceptionSupplier)
: Returns the value if present, otherwise throws an exception created by the provided supplier.Example:
Optional<String> foundName = findNameById(123);
// Provides a default value if the name is not found
String name = foundName.orElse("Unknown");
System.out.println(name);
What are default methods in interfaces?
Default methods were introduced in Java 8. They are methods in an interface that have an implementation. A class that implements the interface inherits the default implementation.
The primary motivation for default methods was to allow the Java Collections API to be evolved without breaking backward compatibility. For example, the forEach
method was added to the Iterable
interface as a default method. If it had been added as a regular abstract method, every class in existence that implemented Iterable
would have been broken and forced to implement it.
A class can choose to:
interface MyInterface {
void regularMethod();
default void newDefaultMethod() {
System.out.println("This is a default implementation.");
}
}
class MyClass implements MyInterface {
@Override
public void regularMethod() {
// ... implementation
}
// MyClass now automatically has newDefaultMethod()
}