Thread
is a class that represents a thread of execution in a program. It can be extended to create a custom thread.
1 | class MyThread extends Thread { |
When we use it, we just simply create a subclass of Thread
and override the run()
method. Then we need to call the start()
method so that Java will internally call our run()
method in a new thread.
Once we extended from Thread, we won’t be able to extend other class. Threads are tightly coupled with tasks, so they are not reusable. We can not call start()
again even if the Thread has finished the execution.
It belongs to thejava.lang.Thread
package.
The Runnable
interface represents a task that can be executed by a Thread
. Instead of creating a new thread instance, we can just pass the task to a Thread object.
1 | Runnable task = new Runnable() { |
There is also a simpler way by using Lambda Expressions in Java 8 and later.
1 | Thread thread = new Thread(() -> System.out.println("Runnable is running...")); |
This approach is generally better than extending from Thread. Because in this way, the task(Runnable) and the thread(Thread) are decoupled. We don’t have to keep creating different instances for same logic process. Also, the lambda expression makes the code simple and easy to understand.
Moreover, Runnable
also supports Thread Pooling. If we directly use Thread, every time we need to execute a task, a new OS-level thread is create, which is expensive in terms of performance. With Runnable
, we can submit a thread to a thread pool. Thread pool reduces the overhead of creating and destroying threads, and the threads are reused, making the execution faster and more efficient.
1 | ExecutorService executor = Executors.newFixedThreadPool(3); |
It belongs to java.lang.Runnable
package.
Callable
is an interface that is similar to Runnable
. But it can have return value and throw an exception.
1 | import java.util.concurrent.*; |
Shorter way:
1 | import java.util.concurrent.*; |
The Callable
must be used with ExecuterService
, the call method doesn’t start a thread directly, all thread is managed by ExecutorService. The result of stored in Future<T>
object.
start()
yet.start()
. However, it might not be in this state immediately due to the availability of CPU. (It is either in the runnable queue for actively executing).Object.wait()
, Thread.join()
, Lock.lock()
can cause a Thread into the waiting state.start()
vs run()
Thread and Runnable: when you call start()
on a Thread object, it will create a new Thread and invokes the run()
methods. The run method you’ve overridden to create your task. So start just start the thread and run defines the task of the thread.
Join()
This method is used for waiting the completion of another thread. When a join is called on another thread t, it will pause the current thread execution and wait t to finish executing.
Here is the definition of method join()
:
1 | public final void join() throws InterruptedException |
So this means the join function can throw an exception. and It doesn’t take any parameter or it can take a long parameter, meaning the timeout. It’s called this way:
1 | Thread tread1 = new Thread(); |
now the code is waiting for thread1 to finish processing after we call the join. It can cause the calling thread to block.
join can be called multiple times on different threads.
When we call it with a parameter, it means the calling thread will wait for the target thread to finish or wait a certain amount of time to expire, whichever comes first.
The monitor is crucial for the wait()
, notify()
, and notifyAll()
methods. These methods require that the thread holds the monitor lock before calling wait()
, and they must also be used to notify waiting threads to resume execution.
wait()
: Causes the current thread to release the lock and enter the waiting state until it is notified.notify()
: Wakes up one thread that is waiting on the object’s monitor.notifyAll()
: Wakes up all threads that are waiting on the object’s monitor.sleep()
vs wait()
Wait method dose not Belong to Thread class.
Fist of all , they are both used to pause the current thread.
sleep()
is used the pause the thread for a specific period of time. After the certain amount of time, the thread will automatically resume the execution. it’s a static method in Thread Class.
wait()
is an instance of method in Object class. It’s used for interthread communication. It let cause the current thread to release a lock it holds and enter wait state until notified by another thread. It coordinate with the notify()
or notifyAll()
methods
And because the wait method is release the monitor lock, it is used in a synchronized block or method.
A daemon thread in Java is a thread that runs in the background and does not prevent the JVM from exiting when the main program finishes execution. Daemon threads are typically used for background tasks such as garbage collection, housekeeping, or background monitoring, where their main purpose is to support the application without blocking its termination.
Thread interference occurs when two or more threads try to access a shared resource concurrently in a way that cause incorrect behavior. Typically, when multiple thread try to modify a shared data without proper synchronization.
For example:
1 | int num = 0; |
Basically, the num++
or num--
will first read the value of num, and then increment or decrement the value of num and then put the new value back to num.
In this way, when thread1 and thread2 try to read value, it’s possible for them to read the same value. This would cause errors to the expected output. This is also called Memory Consistency.
In order to fix this, we want one thread to completely finish the whole process of increment or decrement, then another one can start to the same. That means we want the thread to hold(lock) the access of the variable num in the entire add and subtract process.
1 | int num = 0; |
The is simply use synchronized to add a lock around the code block.
However, for this example, there is a better way to solve this.
It provides a way to work with integers in a thread-safe manner without using synchronization or explicit locks. It allows atomic operations on a single integer value, meaning that the updates to the value are performed as a single, indivisible operation, which prevents interference from other threads.
1 | import java.util.concurrent.atomic.AtomicInteger; |
This keyword is used create a controlled access(Lock) on a code block or method.
There are several ways of using it and the scope of this :
Object level synchronization
1 | public class Counter { |
This only locks the instance(object) of the class. It will only be synchronized when two threads are trying to call the increment method on the same instance of Counter Class.
Class level
1 | public class Counter { |
If two threads try to call increment() even it’s called on a different instance, it will be synchronized.
On code Block
1 | public class Counter { |
This is also an object level synchronization
Class level for static member
1 | public class Counter { |
We pass the Counter.Class
to let it lock the class, it applies to all the instances of Counter Class.
It provides more control and flexibility for lock compared to synchronized. We can manually acquire and release lock.
1 | import java.util.concurrent.locks.ReentrantLock; |
A ReadWriteLock
allows for a more flexible locking strategy where read operations can happen concurrently, but write operations are exclusive. ReentrantReadWriteLock
is the most commonly used implementation of ReadWriteLock
and allows you to have separate locks for reading and writing.
Read Lock: multiple threads can hold the read lock at the same time, as long as there is no thread holds the write lock.
Write Lock: Only one thread can hold the Write Lock, and no other class hold the read or write lock at the same time.
1 | import java.util.concurrent.locks.ReentrantReadWriteLock; |
In order not to violate the mechanism of Read Write Lock, we can check the lock while we are trying to lock.
1 | ReadWriteLock lock = new ReentrantReadWriteLock(); |
Use this keyword to ensures the variable’s value is always read from and written to the main memory. So once the variable is changed, it is visible to all thread. But it does not guarantee the atomicity. So it cannot be used for indivisible step like ++ or —.
1 | private volatile boolean flag = false; |
Semaphore is an class in Java that provides a set of mechanism to manage permits to a resource. We initialize it by telling it how many permits are allowed at the same time. It does not know what resource it’s protecting. Instead, we call the acquire method inside our own logic.
1 | private static final Semaphore semaphore = new Semaphore(3); |
semaphore.acquire()
, it checks if a permit is available. If it is, the thread can proceed; otherwise, it waits. After finishing the work, the thread releases the permit by calling semaphore.release()
.
Its internally synchronized. Every method of hashtable synchronized, so only one thread an operate on the hashtable at one time. Each method will lock the entire map.
It doesn’t allow null key and null values. It is not recommended to use anymore.
It’s is not internally synchronized. It is a wrapper that add external synchronization to existing Map completion. This one also lock the entire map.
It allows only one null key and multiple null values.
This one uses a mechanism called fine-grained locking. It only locks the map segments (buckets). Before Java 8, map are divided into 16 segments, Each segment has its own lock. Java 8 + uses bucket instead of segment. We can modify different segments at the same time. So this one has the fast performance.
ConcurrentHashMap also support Compare-and-Swap for Atomic operations. Like putIfAbsent()
, computeIfPresenet()
, and replace()
. They are atomic without locks.
It doesn’t allow null key and null values.
A thread safe singleton class only allows one instance of this class. It can only be created once and it does not allow destruction.
1 | public class Singleton { |
We can initialize the field at the time of class loading. There is no threads involved yet, so we don’t need to synchronized it. And we make the constructor private to prevent it from creating an instance again.
We only initialize the instance when needed. So we need to make it thread-safe.
1 | public class Singleton { |
It’s important to double-check because after checking the instance and before locking the class, the instance might be created by other threads. We don’t know, so we need to check again.
We need to use volatile keyword to ensure this instance is visible to any thread.
This one is slightly slower and more complex than eager initialization.
This method uses a static inner class to implement the singleton.
1 | public class Singleton { |
It is also lazy initialization. The instance will only be initialized when the getInstance()
method is called.
This is considered the most efficient way. Because Java ensures that Enums are thread-safe and cannot be serialized.
1 | public enum Singleton { |
The four necessary conditions for a deadlock to occur in a system are known as the Coffman conditions, named after the researchers who described them. These conditions must all be met simultaneously for a deadlock to arise in a multi-threaded environment. The four conditions are: