Process: A process is a self contained execution environment and it can be seen as a program or application.
Thread: lightweight.
a process can have multiple threads running, and at least one main thread.
all thread share parent process code and data
thread creation, context switching, and intercommunication are less expensive
Multithreading: In multi-core system, more than one thread can run at the exactly same time. In single-core system, more than one thread can run parallel using OS feature time slicing to share the processor time.
example
implement Runnable: recommended (interface-oriented programming). Support to be more functional class.
publicstaticvoidmain(String[] args){ Threadt1=newThread(newHeavyWorkRunnable(), "t1"); Threadt2=newThread(newHeavyWorkRunnable(), "t2"); System.out.println("Starting Runnable threads"); t1.start(); t2.start(); System.out.println("Runnable Threads has been started"); Threadt3=newMyThread("t3"); Threadt4=newMyThread("t4"); System.out.println("Starting MyThreads"); t3.start(); t4.start(); System.out.println("MyThreads has been started");
} }
daemon threads & non-daemon threads
In Java, a daemon thread is a low-priority thread that runs in the background to perform tasks such as garbage collection and memory management. Daemon threads are usually used to perform tasks that do not require user interaction or direct communication with the user. In Java, you can create a daemon thread by calling the setDaemon(true) method on the Thread object.
When the main thread finishes in a Java program, the JVM will only wait for non-daemon threads to complete before terminating. Daemon threads, on the other hand, will be abruptly terminated when the JVM exits, regardless of their state.
garbage collector daemon thread
If the garbage collector daemon thread is terminated abruptly during the JVM shutdown, it may leave some unreclaimed objects in the heap. The unreclaimed objects will eventually be reclaimed by the operating system when the process terminates.
Calling System.gc() does not start a new thread. Instead, it is a request to the JVM to run the garbage collector. Whether or not the JVM actually runs the garbage collector in response to this request is up to the JVM implementation and cannot be guaranteed.
In general, it is not recommended to call System.gc() explicitly.
In some rare cases, calling System.gc() may be necessary. For example, if your application is generating a large amount of garbage and you want to force garbage collection before a critical section of code, you can call System.gc() to encourage the JVM to collect the garbage. But note that this is generally not recommended unless you have a specific need for it and have thoroughly tested its impact on performance.
Therefore, it’s generally recommended to manage memory explicitly by properly releasing objects when they are no longer needed, rather than relying on the JVM to reclaim them at the end of the process.
blocked thread (wait, notify…)
Object class contains 3 final methods wait, notify, notifyall which allows thread to communicate about the lock status.
wait: block the current thread, until some thread calls notify/notifyall on this object, or after some specified time.
notify: unlock one thread that’s waiting for the object
notifyAll: unlock all threads that’re waiting for the object.
Notifiernotifier=newNotifier(msg); newThread(notifier, "notifier").start(); // here the line will be printed quickly and the main thread is finished // while all the other threads are still running. See thread.join below System.out.println("All the threads are started"); }
}
classMessage { private String msg;
publicMessage(String str){ this.msg=str; }
public String getMsg() { return msg; }
publicvoidsetMsg(String str) { this.msg=str; } }
classWaiterimplementsRunnable{
private Message msg;
publicWaiter(Message m){ this.msg=m; }
@Override publicvoidrun() { Stringname= Thread.currentThread().getName(); synchronized (msg) { try{ System.out.println(name+" waiting to get notified at time:"+System.currentTimeMillis()); msg.wait(); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println(name+" waiter thread got notified at time:"+System.currentTimeMillis()); //process the message now System.out.println(name+" processed: "+msg.getMsg()); } } }
If there are any non-daemon threads still running, they will continue to run even after the main() function has completed. The JVM will keep running until all non-daemon threads have finished executing. Once all non-daemon threads have completed, the JVM will exit.
If you want the main process to wait for all threads to complete before exiting, you can call the join() method on each thread. The join() method will block the calling thread (in this case, the main thread) until the thread being joined completes its execution.
public final void join(): This java thread join method puts the current thread on wait until the thread on which it’s called is dead. If the thread is interrupted, it throws InterruptedException. public final synchronized void join(long millis): This java thread join method is used to wait for the thread on which it’s called to be dead or wait for specified milliseconds. Since thread execution depends on OS implementation, it doesn’t guarantee that the current thread will wait only for given time. public final synchronized void join(long millis, int nanos): This java thread join method is used to wait for thread to die for given milliseconds plus nanoseconds.
Using join with maximum time can avoid some dead lock issues.
Multiple threads create from the same object share the object variables, which may cause issues. To avoid parallel update issues, we can
Do not update in the parallel thread
Synchronization is the easiest and most widely used tool for thread safety in java.
Use of Atomic Wrapper classes from java.util.concurrent.atomic package. For example AtomicInteger
Use of locks from java.util.concurrent.locks package.
Using thread safe collection classes, check this post for usage of ConcurrentHashMap for thread safety.
Using volatile keyword with variables to make every thread read the data from memory, not read from thread cache.
synchronized internally uses locks on Object or Class to make sure only one thread is executing the synchronized code. A monitor in Java is a synchronization mechanism that allows multiple threads to safely access shared resources. A monitor is associated with an object, and when a thread enters a synchronized block on that object, it acquires the monitor lock.
When use synchronization,
use in 2 ways: on entire method or on a code block. Cannot use synchronized on construction code or variables.
Best practice:
use the lowest level of lock. Better lock the required code block instead of the whole method. Lock the instance method in fact use the current Object as the lock, and lock the static method in fact use the current Class as the lock.
Use a private dummy object as the lock. Do not make the lock public or provide public setter, because when the object is changed, multiple threads on this variable maybe parallel running for they in fact locked different object reference.
Better not use the current Object as a lock. It will lock all the fields on the Object, which may block all the synchronized code on this class when there’re many synchronized blocks.
Better not use any object maintained in a constant pool, e.g. String. It may block some completely unrelated synchronized code on the same String.
Avoid Nested Locks: This is the most common reason for deadlocks, avoid locking another resource if you already hold one. It’s almost impossible to get deadlock situation if you are working with only one object lock.
Lock Only What is Required
Avoid waiting indefinitely: You can get deadlock if two threads are waiting for each other to finish indefinitely using thread join. If your thread has to wait for another thread to finish, it’s always best to use join with maximum time you want to wait for thread to finish.
thread dump
To detect dead lock, we can use thread dump.
Java thread dump is list of all the thread active in JVM. Each entry is in the following format:
Thread Name: Name of the Thread
Thread Priority: Priority of the thread
Thread ID: Represents the unique ID of the Thread
Thread Status: Provides the current thread state, for example RUNNABLE, WAITING, BLOCKED. While analyzing deadlock look for the blocked threads and resources on which they are trying to acquire lock.
Thread callstack: Provides the vital stack information for the thread. This is the place where we can see the locks obtained by Thread and if it’s waiting for any lock.
To get thread dump:
VisualVM Profiler: If you are analyzing application for slowness, you must use a profiler. We can generate thread dump for any process using VisualVM profiler very easily. You just need to right click on the running process and click on “Thread Dump” option to generate it.
jstack: Java comes with jstack tool through which we can generate thread dump for a java process. This is a two step process.
Find out the PID of the java process using ps -eaf | grep java command
Run jstack tool as jstack PID to generate the thread dump output to console, you can append thread dump output to file using command “jstack PID >> mydumps.tdump”
We can use kill -3 PID command to generate the thread dump. This is slightly different from other ways to generate thread dump. When kill command is issued, thread dump is generated to the System out of the program. So if it’s a java program with console as system out, the thread dump will get printed on the console. If the java program is a Tomcat server with system out as catalina.out, then thread dump will be generated in the file.
Java 8 has introduced jcmd utility. You should use this instead of jstack if you are on Java 8 or higher. Command to generate thread dump using jcmd is jcmd PID Thread.print.
"DestroyJavaVM" prio=5 tid=0x00007fb0a2801000 nid=0x1703 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE
"t3" prio=5 tid=0x00007fb0a204b000 nid=0x4d07 waiting for monitor entry [0x000000015d971000] java.lang.Thread.State: BLOCKED (on object monitor) at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41) - waiting to lock <0x000000013df2f658> (a java.lang.Object) - locked <0x000000013df2f678> (a java.lang.Object) at java.lang.Thread.run(Thread.java:722)
"t2" prio=5 tid=0x00007fb0a1073000 nid=0x4207 waiting for monitor entry [0x000000015d209000] java.lang.Thread.State: BLOCKED (on object monitor) at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41) - waiting to lock <0x000000013df2f678> (a java.lang.Object) - locked <0x000000013df2f668> (a java.lang.Object) at java.lang.Thread.run(Thread.java:722)
"t1" prio=5 tid=0x00007fb0a1072000 nid=0x5503 waiting for monitor entry [0x000000015d86e000] java.lang.Thread.State: BLOCKED (on object monitor) at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41) - waiting to lock <0x000000013df2f668> (a java.lang.Object) - locked <0x000000013df2f658> (a java.lang.Object) at java.lang.Thread.run(Thread.java:722)
"Finalizer" daemon prio=5 tid=0x00007fb0a4800000 nid=0x3f03 in Object.wait() [0x000000015d0c0000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x000000013de75798> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:135) - locked <0x000000013de75798> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:151) at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:177)
"Reference Handler" daemon prio=5 tid=0x00007fb0a4002000 nid=0x3e03 in Object.wait() [0x000000015cfbd000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x000000013de75320> (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Object.java:503) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:133) - locked <0x000000013de75320> (a java.lang.ref.Reference$Lock)
"VM Periodic Task Thread" prio=5 tid=0x00007fb0a1015000 nid=0x5403 waiting on condition
JNI global references: 114
Found one Java-level deadlock: ============================= "t3": waiting to lock monitor 0x00007fb0a1074b08 (object 0x000000013df2f658, a java.lang.Object), which is held by "t1" "t1": waiting to lock monitor 0x00007fb0a1010f08 (object 0x000000013df2f668, a java.lang.Object), which is held by "t2" "t2": waiting to lock monitor 0x00007fb0a1012360 (object 0x000000013df2f678, a java.lang.Object), which is held by "t3"
Java stack information for the threads listed above: =================================================== "t3": at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41) - waiting to lock <0x000000013df2f658> (a java.lang.Object) - locked <0x000000013df2f678> (a java.lang.Object) at java.lang.Thread.run(Thread.java:722) "t1": at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41) - waiting to lock <0x000000013df2f668> (a java.lang.Object) - locked <0x000000013df2f658> (a java.lang.Object) at java.lang.Thread.run(Thread.java:722) "t2": at com.journaldev.threads.SyncThread.run(ThreadDeadlock.java:41) - waiting to lock <0x000000013df2f678> (a java.lang.Object) - locked <0x000000013df2f668> (a java.lang.Object) at java.lang.Thread.run(Thread.java:722)
Found 1 deadlock.
ThreadLocal
Java ThreadLocal is used to create thread local variables.
// SimpleDateFormat is not thread-safe, so give one to each thread privatestaticfinal ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.<SimpleDateFormat>withInitial (() -> {returnnewSimpleDateFormat("yyyyMMdd HHmm");});
@Override publicvoidrun() { System.out.println("Thread Name= "+Thread.currentThread().getName()+" default Formatter = "+formatter.get().toPattern()); try { Thread.sleep(newRandom().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } //formatter pattern is changed here by thread, but it won't reflect to other threads formatter.set(newSimpleDateFormat());
@Override public String call()throws Exception { Thread.sleep(1000); return Thread.currentThread().getName(); }
publicstaticvoidmain(String args[]){ //Get ExecutorService from Executors utility class, thread pool size is 10 ExecutorServiceexecutor= Executors.newFixedThreadPool(10); //create a list to hold the Future object associated with Callable List<Future<String>> list = newArrayList<Future<String>>(); //Create MyCallable instance Callable<String> callable = newMyCallable(); for(int i=0; i< 100; i++){ //submit Callable tasks to be executed by thread pool Future<String> future = executor.submit(callable); //add Future to the list, we can get return value using Future list.add(future); } for(Future<String> fut : list){ try { //print the return value of Future, notice the output delay in console // because Future.get() waits for task to get completed System.out.println(newDate()+ "::"+fut.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } //shut down the executor service now executor.shutdown(); }
}
Thread Pool Framework
Tread pool is a way to manage the pool of worker threads. It contains a queue that keeps tasks waiting to get executed. There’re 3 main classes:
Executors : factory class to create all kinds of thread pool
some executor pools
The work queue size of an ExecutorService in Java depends on the type of the work queue implementation used.
If the ExecutorService is created using the Executors.newFixedThreadPool(int nThreads) method, which creates a fixed-size thread pool, then the work queue size is unbounded. This means that tasks submitted to the thread pool will wait in an unbounded queue until a thread is available to execute them.
If the ExecutorService is created using the Executors.newCachedThreadPool() method, which creates a thread pool that creates new threads as needed, then the work queue size is also unbounded.
If the ExecutorService is created using the Executors.newSingleThreadExecutor() method, which creates a single thread executor that executes tasks sequentially in the order they are submitted, and the work queue size is also unbounded. The main difference between newSingleThreadExecutor() and newFixedThreadPool(1) is the order of execution of submitted tasks. If you need to ensure that tasks are executed in the order they are submitted, you should use newSingleThreadExecutor(). If you need to limit the number of concurrent tasks to one but do not care about the order of execution, you can use newFixedThreadPool(1).
Unbounded work queue size can potentially cause memory issues if the queue grows too large.
If you want to create an ExecutorService with a bounded work queue size, you can use the ThreadPoolExecutor class and specify the maximum size of the work queue when creating the thread pool. In the following example, the ThreadPoolExecutor has the initial pool size as 2, maximum pool size to 4 and work queue size as 2. So if there are 4 running tasks and more tasks are submitted, the work queue will hold only 2 of them and the rest of them will be handled by RejectedExecutionHandlerImpl.
publicstaticvoidmain(String args[])throws InterruptedException{ //RejectedExecutionHandler implementation RejectedExecutionHandlerImplrejectionHandler=newRejectedExecutionHandlerImpl(); //Get the ThreadFactory implementation to use ThreadFactorythreadFactory= Executors.defaultThreadFactory(); //creating the ThreadPoolExecutor with initial pool size as 2, maximum pool size to 4 and work queue size as 2 ThreadPoolExecutorexecutorPool=newThreadPoolExecutor(2, 4, 10, TimeUnit.SECONDS, newArrayBlockingQueue<Runnable>(2), threadFactory, rejectionHandler); //start the monitoring thread MyMonitorThreadmonitor=newMyMonitorThread(executorPool, 3); ThreadmonitorThread=newThread(monitor); monitorThread.start(); //submit work to the thread pool for(int i=0; i<10; i++){ executorPool.execute(newWorkerThread("cmd"+i)); }
Thread.sleep(30000); //shut down the pool executorPool.shutdown(); //shut down the monitor thread Thread.sleep(5000); monitor.shutdown();
java.util.TimerTask is an abstract class that implements Runnable interface and we need to extend this class to create our own TimerTask that can be scheduled using java Timer class.
While scheduling tasks using Timer, you should make sure that time interval is more than normal thread execution, otherwise tasks queue size will keep growing and eventually task will be executing always.
Java 1.5 Concurrency API came up with java.util.concurrent.locks package with Lock interface and some implementation classes to improve the Object locking mechanism. Some important interfaces and classes in Java Lock API are:
Lock: This is the base interface for Lock API. It provides all the features of synchronized keyword with additional ways to create different Conditions for locking, providing timeout for thread to wait for lock. Some of the important methods are lock() to acquire the lock, unlock() to release the lock, tryLock() to wait for lock for a certain period of time, newCondition() to create the Condition etc.
Condition: Condition objects are similar to Object wait-notify model with additional feature to create different sets of wait. A Condition object is always created by Lock object. Some of the important methods are await() that is similar to wait() and signal(), signalAll() that is similar to notify() and notifyAll() methods.
ReadWriteLock: It contains a pair of associated locks, one for read-only operations and another one for writing. The read lock may be held simultaneously by multiple reader threads as long as there are no writer threads. The write lock is exclusive.
ReentrantLock: This is the most widely used implementation class of Lock interface. This class implements the Lock interface in similar way as synchronized keyword. Apart from Lock interface implementation, ReentrantLock contains some utility methods to get the thread holding the lock, threads waiting to acquire the lock etc. synchronized block are reentrant in nature i.e if a thread has lock on the monitor object and if another synchronized block requires to have the lock on the same monitor object then thread can enter that code block. I think this is the reason for the class name to be ReentrantLock.
publicvoiddoSomething(){ //do some operation, DB read, write etc }
publicvoiddoLogging(){ //logging, no need for thread safety } }
lock vs synchronized
Use synchronized if the code section you want to lock is simple and doesn’t need to perform any complex operations. synchronized is simpler and more readable than Lock.
Use Lock if you need more advanced features, such as fairness, re-entrancy, and the ability to try acquiring a lock without blocking. Lock is more flexible and can provide better performance than synchronized in some situations.
Always use the try-finally pattern when using Lock to ensure that the lock is released even if an exception is thrown.
In summary, synchronized is easier to use and sufficient for most cases, while Lock provides more advanced features and better performance in certain situations. Lock provides more control and flexibility, but requires more code and is more error-prone, while synchronized is simpler and easier to use, but may have more contention issues in high concurrency scenarios.