Multithreading

Multithreading

  • Inter-thread communication 
Each thread has its own stack, which also means its own copy of shared global variables ( static and non-static both ).  so updates from one thread may not necessarily be visible to another in time.  This will cause inconsistencies. This is especially important in multi-core systems where each thread might end up on a different core and each core may have it its own local cache which the thread is using. The updates by one thread may not be readily synced with main cache and hence not visible to other threads.

The same issue may also arise due to compiler optimizations;  the sequence of instructions may get reordered by jvm and compiler.  This will still produce consistent results within single thread but other threads are not guaranteed to see this consistency.

Java provides two ways to deal with  these issues.  synchronized access, volatile variables.   When you try to have synchronized access of a code block or method, only one thread is having access to that code. The Java guarantees that when one thread holds the lock ( or has access to the synchronized block) it sees all the updates ( up to the time they released the lock by leaving synchronized code block)  from all other threads which held the same lock before it.  Does not matter whether JVM reordered some instructions or multiple cores were updating different caches.

On the other hand  Java guarantees that when a thread reads ( read or write )  a  volatile variable  it always sees the latest updates made by other threads up to that point. When a thread writes to a volatile variable, it gets flushed to the main memory and change becomes visible to all other threads.  So writing to volatile is similar to leaving a synchronized block and reading the similar to entering a synchronized block.  But volatile access is non blocking, which means between reading and writing by the single thread, if some other thread modifies the value, that will be lost.

For a synchronized code block, you can decide for  which object you want to get the lock, while for synchronized method, it is always for the  instance of the object on which method is called. In case of static methods the lock is obtained on Class object. 

When a thread acquires the intrinsic lock on an object, other threads must wait until the lock is released. However, the thread that currently owns the lock can acquire it multiple times without any problem ( Ref : https://www.callicoder.com/java-concurrency-issues-and-thread-synchronization/ ) 

  • In java, every object has one intrinsic lock associated with it.
  • In JVM there are two kinds of threads , deamon and non-daemon. When the last  non-daemon threads ends ( typically main thread) , the jvm also terminates. 
  • In Hotspot JVM each JVM 
  • start() method actually starts a new thread which in turn executes the code inside run() method for same object.  If you directly call run() method, it will execute the code in the current thread ,without creating a new thread. 
  • Implement Runnable/Callable vs. extending Thread. Implementing Runnable better as it separates implementation of task from execution. Also thread implementation will be much flexible as it can implement any other interface and extend another class rather than Thread. 
  • Reentrant Synchronization: Allowing a thread to acquire the same lock more than once enables reentrant synchronizationWithout reentrant synchronization, a synchronized code block could not invoke another synchronized method as it would have resulted in a deadlock. 
  • starvation 
  • livelock
  • ConcurrentHashmap
  • CyclicBarrier
  • CountDownLatch
  • Atomic Operations -  Read and write are atomic for all reference variables. All primitive variable( except long and double ) read/write are also atomic. All volatile variable read/write ( including long and double types also ) are atomic.   
  • count++  is not atomic operation. 
  • You can not use Thread.getCurrentThread.join() in the main  with threadpool API ( ExecutorService etc..) . Threadpool API provides different mechanism to wait for threads to complete.  In threadpool, when a task is finished, it goes into parked state. So if you use join() , it will never return  because all the threadpool threads are in parked state and wont get out of that  as main thread is waiting those to finish/terminate.
  • Executor( super class) ->ExecutorService->ScheduledExecutorService
  • WeakHashMap - holds weak references for keys so that key/value pairs can be garbage collected when no external reference for the keys exist in the JVM. 
  • EnumHashMap - Array based ( fast )  HashMap implementation for holding enum mappings( enums as keys ).
  • IdentityHashMap - This is hash table based map implementation which utilizes reference equality in place of object equality while comparing keys.  This implementation is used where reference equality is important like object deep copy / serialization etc ( for maintaining node tables ). 
  • Strong Reference, Soft Reference( SoftReference<T>), Weak Reference (WeakReference<T> ) All soft references to softly-reachable objects are guaranteed to have been cleared before the virtual machine throws an OutOfMemoryError. 
  • default clone() method is shallow copy. It copies the object references, so the references in copy also point to same original objects which the references in original object point.








Problems with old memory model

  • Initial java memory model had lot of issues and difficult to understand for developers.

  • volatile and final did not work the way they were supposed to work.

Fixes in new memory model ( JSR133)

  • new memory model guaranteed the order of memory operations and thread operations which could be understood by developers easily. This allowed developers to predict the result in multithreaded enviornment despite compiler and runtime/cpu/hardware optimizations.

  • Fixed final and volatile behavior.


    So in jvm 1.5 onwards we do have a fix for broken "Double Checked Locking". declare the instance volatile. We can truely create a Multithreaded Singleton.

    public class Something {
      private volatile static Something instance = null;

      public Something getInstance() {
         if (instance == null) {
          synchronized (this) {
              if (instance == null)
                   instance = new Something();
          }
       }
       return instance;
      }
    }

    There is another alternative. This is called "Initialization On Demand"


    private static class LazySomethingHolder {
        public static Something something = new Something();


       public static Something getInstance() {
         return LazySomethingHolder.something;
       }
References: 

Comments

Popular posts from this blog

SQL

Analytics

DBeaver