Forgiving What You Can't Forget: Discover How to Move On, Make Peace with Painful Memories, and Create a Life That’s Beautiful Again
72% OffPULIDIKI Car Cleaning Gel Detailing Putty Interior Cleaner Slime Car Accessories Stocking Stuffers for Men Women Teens White Elephant Gifts for Adults
37% OffJava provides built-in support for multithreaded programming. A thread is an independent path of execution within a program. Multithreading allows you to take advantage of multiple CPU cores by executing multiple threads in parallel. Concurrency refers to dealing with lots of things at once. In Java, concurrency is achieved through multithreading.
Why Use Multithreading?
There are several reasons to use multithreading in your Java programs:
- Improved performance: By executing tasks concurrently on separate threads, you can improve performance and responsiveness. This is especially true when threads are running on separate CPU cores.
- Better resource utilization: Threads allow multiple tasks to share scarce resources like memory and CPU cycles without conflict.
- Simplified modeling: Multithreading can simplify modeling and implementation of concurrent activities that may be encountered in real-world applications.
- Convenient handling of asynchronous events: Multithreading makes it easy to handle asynchronous events like user input concurrently with main program execution.
Thread Lifecycle
The lifecycle of a thread in Java consists of several discrete stages:
- New: A new thread instance is created using the
Thread
class constructor. The thread at this point has no runtime behavior associated with it. - Runnable: The newly born thread calls the
start()
method, which makes it eligible to run and be scheduled by the thread scheduler. The thread may now run at anytime. - Running: The JVM thread scheduler picks the thread to execute its target
run()
method. A running thread may be suspended or halted by the scheduler at anytime.
–Blocked/Waiting: The thread is still alive but is currently not eligible to run. This may happen if it is waiting on a monitor lock or some I/O operation.
- Terminated: The
run()
method completes execution. Alternatively,stop()
may have been called. The thread is dead and cannot be restarted.
Thread States
During its lifetime, a thread transitions through various states. The main thread states are:
- New: Just instantiated using
Thread
constructor - Runnable: Eligible to run on CPU when scheduled
- Running: Currently being executed by a CPU core
- Waiting: Waiting on some monitor lock or I/O operation
- Timed Waiting: Waiting with specified waiting time
- Blocked: Waiting to acquire a monitor lock
- Terminated: Execution completed
Creating and Starting Threads
There are two ways to create a new thread in Java:
- By extending the
Thread
class - By implementing the
Runnable
interface
Extending Thread class
You can create a new thread by extending the Thread
class and overriding its run()
method:
public class MyThread extends Thread {
@Override
public void run() {
// thread task
}
}
JavaScriptTo start the thread, create an instance and call its start()
method:
MyThread thread = new MyThread();
thread.start();
JavaScriptImplementing Runnable Interface
The Runnable
interface defines a single run()
method:
public interface Runnable {
public void run();
}
JavaScriptYou can create a runnable task like so:
public class MyTask implements Runnable {
@Override
public void run() {
// thread task
}
}
JavaScriptThen pass an instance to a Thread
to execute the task:
Runnable task = new MyTask();
new Thread(task).start();
JavaScriptRunnable
is generally preferred over extending Thread
since Java does not support multiple inheritance.
Thread Priority
Thread priority indicates to the scheduler the relative desirability of scheduling a thread. Java provides ten priority levels from MIN_PRIORITY
(1) to MAX_PRIORITY
(10).
By default, every thread gets priority NORM_PRIORITY
(5). To change the priority:
thread.setPriority(Thread.MAX_PRIORITY);
JavaScriptHigher priority threads get executed in preference to those with lower priority. However, thread priorities cannot guarantee ordering. They are just hints to the scheduler.
Daemon Threads
A daemon thread only runs in the background to provide services to user threads. Its defining feature is that the JVM exits when only daemon threads remain.
To mark a thread as daemon, simply call setDaemon(true)
before starting it:
thread.setDaemon(true);
thread.start();
JavaScriptDaemon threads are useful for services like garbage collection and monitoring.
Thread Synchronization
Thread synchronization refers to controlling the timing and order of thread execution to avoid unpredictable concurrent access problems. This is achieved through synchronized blocks and methods.
Synchronized Block
To create a synchronized block, simply add the synchronized
keyword:
synchronized(lock) {
// access shared data
}
JavaScriptThis block will execute after acquiring the intrinsic lock on lock
. Other threads trying to access the block must wait.
Synchronized Method
Marking a method as synchronized makes it acquire a lock on the owning instance before execution:
synchronized void add(int value) {
// update shared data
}
JavaScriptStatic synchronized methods lock on the class instance instead.
Reentrant Locks
ReentrantLock
is a more flexible alternative to implicit monitor locking. Key features include:
- Ability to hold lock over multiple method calls.
- Multiple condition variables for finer notification control.
- Non-blocking
tryLock()
method.
Basic usage:
Lock lock = new ReentrantLock();
lock.lock();
try {
// access resource
}
finally {
lock.unlock();
}
JavaScriptVolatile Variables
The volatile
keyword indicates that a variable’s value will be modified from concurrent threads. Its key behaviors include:
- Updates to volatile variables are immediately visible across threads.
- Volatile reads do not cache or re-order writes from other threads.
Volatile variables provide happens-before relationship and synchronization visibility guarantees. But they do not provide atomicity or mutual exclusion semantics.
Thread Cooperation
Threads need ways to safely communicate results and coordinate execution. Java provides several mechanisms for thread cooperation.
Wait and Notify
Objects can call wait()
to pause thread execution and notify()/notifyAll()
to wake waiting threads. This provides basic signaling between threads:
// Waiting thread
syncObject.wait();
// Notifying thread
syncObject.notifyAll();
JavaScriptJoining Threads
The join()
method allows a thread to wait for completion of another:
Thread t2 = new Thread(...);
t2.start();
t2.join(); // wait for t2 to finish
JavaScriptThis enables coordinating the execution order of interdependent threads.
ThreadLocal
A ThreadLocal
variable creates a separate instance accessible only to the owning thread. This provides simple thread-safe sharing without explicit locking.
private ThreadLocal counter = new ThreadLocal();
counter.set(1); // per-thread instance
JavaScriptThread Pools
Managing a large number of concurrent threads can be error-prone and resource intensive. The solution is to use a thread pool backed by a queue.
Java provides ExecutorService for creating and using thread pools:
// single background thread
ExecutorService pool = Executors.newSingleThreadExecutor();
// reusabled cached thread pool
ExecutorService pool = Executors.newCachedThreadPool();
// start tasks
for(Task task : tasks)
pool.execute(task);
// shutdown gracefully
pool.shutdown();
JavaScriptThe pool handles queueing and reuse, providing better control over concurrency.
Thread Safety Tips
Here are some tips for writing thread-safe code in Java:
- Use synchronized blocks/methods judiciously after identifying critical sections.
- Prefer concurrent collections and atomic variables over synchronization.
- Avoid shared mutable state by using ThreadLocal and immutable objects.
- Use ExecutorService/thread pools instead of creating threads manually.
- Leverage copy-on-write collections like ConcurrentHashMap.
- Declare methods as synchronized only if needed.
- Catch ConcurrentModificationException to detect thread interference.
- Document thread safety guarantees for custom classes and methods.
Conclusion
Multithreading enables efficient utilization of multiprocessor architectures prevalent today. Java provides extensive inbuilt support through a rich concurrency framework. Understanding thread lifecycle, synchronization, locks, thread pools and high level concurrency APIs is key to harnessing the power of Java multithreading. Used judiciously, multithreading can simplify development of robust real-world applications.