Concurrency || Collection - ashish-ghub/docs GitHub Wiki

Concurrency

Understanding Thread Interruption in Java

https://praveer09.github.io/technology/2015/12/06/understanding-thread-interruption-in-java/#:~:text=In%20Java%2C%20one%20thread%20cannot,as%20true%20on%20the%20instance.

In Java, one thread cannot stop the other thread. A thread can only request the other thread to stop. The request is made in the form of an interruption. Calling the interrupt() method on an instance of a Thread sets the interrupt status state as true on the instance.

Summary

The answers to the two questions that I had set out to answer are:

How to request a task, running on a separate thread, to finish early?

Use interruption. If using Thread directly in your code, you may call interrupt() on the instance of thread. If using Executor framework, you may cancel each task by calling cancel() on Future

If using Executor framework, you may shutdown the ExecutorService by calling the shutdownNow() method.

How to make a task responsive to such a finish request?

Handle interruption request, which in most of the cases is done by handling InterruptedException. Also preserve the interruption status by calling Thread.currentThread().interrupt().

The Java Memory Model - The Basics

https://www.youtube.com/watch?v=LCSqZyjBwWA&list=PLL8woMHwr36G7eI_3r4-sNKcEVQstTJck&index=0

what is thread stack?

  • a thread consist of own copy of local variable and local object references which is not shared between the threads.
  • local variables are thread safe

Java Happens Before Guarantee - Java Memory Model - Part 2 https://www.youtube.com/watch?v=oY14UyP61F8&list=PLL8woMHwr36G7eI_3r4-sNKcEVQstTJck&index=2

Java-future

https://www.baeldung.com/java-future

Future class represents a future result of an asynchronous computation – a result that will eventually appear in the Future after the processing is complete.

Let's see how to write methods that create and return a Future instance.

Long running methods are good candidates for asynchronous processing and the Future interface. This enables us to execute some other process while we are waiting for the task encapsulated in Future to complete.

Some examples of operations that would leverage the async nature of Future are:

  1. computational intensive processes (mathematical and scientific calculations)
  2. manipulating large data structures (big data)
  3. remote method calls (downloading files, HTML scrapping, web services).

Sample:

import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future;

public class CacheLoader {

private ExecutorService executor = Executors.newSingleThreadExecutor();

public Future<Cache> loadCache() {
    return executor.submit(() -> {
        // Perform the time-consuming task of loading the cache
        Cache cache = new Cache();
        // load data into cache here
        return cache;
    });
}

public static void main(String[] args) throws Exception {
    CacheLoader cacheLoader = new CacheLoader();
    Future<Cache> cacheFuture = cacheLoader.loadCache();

    // Do other tasks while the cache is loading
    // ...

    // Retrieve the cache when it's ready
    Cache cache = cacheFuture.get();

    // Use the cache
    // ...
 }

}


java8 -completablefuture useful article must read

Java 8 LongAdders: The Right Way To Manage Concurrent Counters:

https://blog.overops.com/java-8-longadders-the-fastest-way-to-add-numbers-concurrently/

Java 8 StampedLocks vs. ReadWriteLocks and Synchronized

https://blog.overops.com/java-8-stampedlocks-vs-readwritelocks-and-synchronized/

Java Concurrency: Read / Write Locks

https://dzone.com/articles/java-concurrency-read-write-lo

Immutable class

  • The class must be declared as final (So that child classes can’t be created)
  • Data members in the class must be declared as final (So that we can’t change the value of it after object creation)
  • A parameterized constructor
  • Getter method for all the variables in it and Getter for object reference needs to be made immutable (see below Date, List as a getter how it can break)
  • No setters(To not have the option to change the value of the instance variable)

Example to create Immutable class

Q. Create a immutable employee class has name, doj , salary, Hobbies list

public class Immutable {

private final String name;

private Date dateOfBirth;

private List<String> hobbies;

public Immutable(String name, Date dateOfBirth, List<String> hobbies) {
    this.name = name;
    this.dateOfBirth = dateOfBirth;
    this.hobbies = hobbies;
}

public String getName() {
    return name;
}

public Date getDateOfBirth() {
    return dateOfBirth;  // not safe date is not immutable
}

public List<String> hobbies() {
   return hobbies ;  // not safe as list is not immutable 
}

}

  • 1 getName() is fine as it returns immutable object as well. However the getDateOfBirth() method can break immutability because the client code can modify returned object, hence modifying the Immutable object as well:

Immutable imm = new Immutable("John", new Date());

imm.getName(); //safe

Date dateOfBirth = imm.getDateOfBirth();

dateOfBirth.setTime(0); //we just modified imm object not safe

  • 2 It is safe to return immutable objects and primitives (as they are returned by value). However you need to make defensive copies of mutable objects, like Date:

public Date getDateOfBirth() { return new Date(dateOfBirth.getTime()); }

  • 3 and wrap collections in immutable views (if they are mutable), e.g. see Collections.unmodifiableList():

public List<Integer> getHobbies() { return Collections.unmodifiableList(hobbies); }

Choosing between synchronized and ReentrantLock

  • ReentrantLock is an advanced tool for situations where intrinsic locking is not practical. Use it if you need its advanced features: timed, polled, or interruptible lock acquisition, fair queueing, or non-block-structured locking. Otherwise, prefer synchronized.

  • ReentrantLock provides the same locking and memory semantics as intrinsic locking, as well as additional features such as timed lock waits, interruptible lock waits, fairness, and the ability to implement non-block-structured locking. The performance of ReentrantLock appears to dominate that of intrinsic locking, winning slightly on Java 6 and dramatically on Java 5.0. So why not deprecate synchronized and encourage all new concurrent code to use ReentrantLock?

  • Intrinsic locks still have significant advantages over explicit locks. Reentrant- Lock is definitely a more dangerous tool than synchronization; if you forget to wrap the unlock call in a finally block, your code will probably appear to run properly, but you’ve created a time bomb that may well hurt innocent bystanders. Save ReentrantLock for situations in which you need something ReentrantLock provides that intrinsic locking doesn’t.

  • ReentrantLock is an advanced tool for situations where intrinsic locking is not practical. Use it if you need its advanced features: timed, polled, or interruptible lock acquisition, fair queueing, or non-block-structured locking. Otherwise, prefer synchronized.

Under Java 5.0, intrinsic locking has another advantage over ReentrantLock:

thread dumps show which call frames acquired which locks and can detect and identify deadlocked threads. The JVM knows nothing about which threads hold ReentrantLocks and therefore cannot help in debugging threading problems using ReentrantLock. This disparity is addressed in Java 6 by providing a management and monitoring interface with which locks can register, enabling locking information for ReentrantLocks to appear in thread dumps and through other management and debugging interfaces. The availability of this information for debugging is a substantial, if mostly temporary, advantage for synchronized; locking information in thread dumps has saved many programmers from utter consternation. The non-block-structured nature of ReentrantLock still means that lock acquisitions cannot be tied to specific stack frames, as they can with intrinsic locks.

High performing caching


public class LRUCache extends LinkedHashMap {
   /**
     * The read-write lock can be re-entered to ensure concurrent read-write security.
     */
    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private Lock readLock = readWriteLock.readLock();
    private Lock writeLock = readWriteLock.writeLock();
   /**
     * Cache Size Limit
     */
    private int maxSize;
   public LRUCache(int maxSize) {
        super(maxSize + 1, 1.0f, true);
        this.maxSize = maxSize;
    }
  @Override
    public Object get(Object key) {
        readLock.lock();
        try {
            return super.get(key);
        } finally {
            readLock.unlock();
        }
    }
    @Override
    public Object put(Object key, Object value) {
        writeLock.lock();
        try {
            return super.put(key, value);
        } finally {
            writeLock.unlock();
        }
    }
    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return this.size() > maxSize;
    }
}

Semaphore : Semaphore limit the number of concurrent threads accessing a specific resource.


Use-case : To limit the resource utilization 1) allow N no of parallel computation or 2) allow N user to login to system

acquire() : Acquires a permit from this semaphore, blocking until one is available, or the thread is interrupted.

package com.concurrent;

import java.util.Map;

import java.util.concurrent.ConcurrentHashMap;

import java.util.concurrent.Semaphore;

public class SemaphoreBounded {

private final Map<String, String> lockMap = new ConcurrentHashMap<>();
private final Semaphore semaphore;

public SemaphoreBounded(int maxComputation)
{
  this.semaphore = new Semaphore(maxComputation);
}

public interface Task<T>
{
  T execute();
}

public <T> T process(String specIdLock, Task<T> task)
{
  String lockId = lockMap.putIfAbsent(specIdLock, specIdLock);
  String localLock = lockId != null?lockId:specIdLock;

  synchronized (localLock)
  {
     try
     {
        semaphore.acquire();
        task.execute();
     }
     catch (InterruptedException e)
     {
        // TODO log error here
     }
     finally
     {
        semaphore.release();
     }

  }

  return null;

}

public void removeFromCache(String specId)
{
  lockMap.remove(specId);
}

}


Collection

  • hashtable-vs-hashmap-vs-concurrenthashmap**

https://medium.com/@mr.anmolsehgal/hashtable-vs-hashmap-vs-concurrenthashmap-4aa0ff1eecc4

  • java-hashmap-internal-implementation

https://medium.com/@mr.anmolsehgal/java-hashmap-internal-implementation-21597e1efec3

  • concurrenthashmap-internal-working-in-java

https://medium.com/@mr.anmolsehgal/concurrenthashmap-internal-working-in-java-b2a1a48c7289

  • concurrenthashmap details

https://medium.com/@itsromiljain/curious-case-of-concurrenthashmap-90249632d335

  • ConcurrentHashMap isn't always enough

https://dzone.com/articles/concurrenthashmap-isnt-always-enough

  • Hashing

https://www.baeldung.com/cs/hashing

Issue : Race condition when 2 threads try it

class A {

 private Map<String, Object> theMap = new ConcurrentHashMap<>();
public Object getOrCreate(String key) {
	Object value = theMap.get(key);
	if (value == null) {
		value = new Object();
		theMap.put(key, value);
	}
	return value;
}

}

solution 1: not good

public synchronized Object getOrCreate(String key) {
	Object value = theMap.get(key);
	if (value == null) {
		value = new Object();
		theMap.put(key, value);
	}
	return value;
}

solution 2 : Good

A much better approach should be using Java 8 Map's computeIfAbsent(K key, Function mappingFunction), which, in ConcurrentHashMap's implementation runs atomically:

private Map<String, Object> theMap = new ConcurrentHashMap<>();

public Object getOrCreate(String key) {
	return theMap.computeIfAbsent(key, k -> new Object());
}

The atomicity of computeIfAbsent(..) assures that only one new Object will be created and put into theMap, and it'll be the exact same instance of Object that will be returned to all threads calling the getOrCreate function. Here, not only the code is correct, it's also cleaner and much shorter.

public class ListHelper { public List list = Collections.synchronizedList(new ArrayList()); ... public synchronized boolean putIfAbsent(E x) { boolean absent = !list.contains(x); if (absent) list.add(x); return absent; }

  • ArrayList Vs CopyOnWriteArray

Both ArrayList and CopyOnWriteArray implement List interface. But There are lots of differences between ArrayList and CopyOnWriteArrayList:

  1. CopyOnWriteArrayList creates a Cloned copy of underlying ArrayList, for every update operation at certain point both will synchronized automatically which is takes care by JVM. Therefore there is no effect for threads which are performing read operation. Therefore thread-safety is not there in ArrayList whereas CopyOnWriteArrayList is thread-safe.

  2. While Iterating ArrayList object by one thread if other thread try to do modification then we will get Runt-time exception saying ConcurrentModificationException. Where as We won’t get any Exception in the case of CopyOnWriteArrayList.

  3. ArrayList is introduced in JDK 1.2 whereas CopyOnWriteArrayList is introduced by SUN people in JDK 1.5.

  4. Iterator of ArrayList can perform remove operation while iteration. But Iterator of CopyOnWriteArrayList cant perform remove operation while iteration, otherwise it will throw run-time exception UnsupportedOperationException.

Below is the implementation of this point.

// Java program to illustrate ArrayList

import java.util.*;

class CopyDemo {

public static void main(String[] args)  
{ 
    ArrayList l = new ArrayList(); 
    l.add("A"); 
    l.add("B"); 
    l.add("C"); 
    Iterator itr = l.iterator(); 
      
    while (itr.hasNext())  
    { 
        String s = (String)itr.next(); 
          
        if (s.equals("B")) 
        { 
            // Can remove 
            itr.remove(); 
        } 
    } 
    System.out.println(l); 
} 

}

⚠️ **GitHub.com Fallback** ⚠️