Skip to content

Latest commit

 

History

History
215 lines (183 loc) · 14.7 KB

File metadata and controls

215 lines (183 loc) · 14.7 KB

Concurrency

What is usage of wait/notify methods?

wait(). It tells the calling thread to give up the lock and go to sleep until some other thread enters the same monitor and calls notify(). notify(). It wakes up one single thread that called wait() on the same object. It should be noted that calling notify() does not actually give up a lock on a resource. It tells a waiting thread that that thread can wake up. However, the lock is not actually given up until the notifier’s synchronized block has completed. notifyAll(). It wakes up all the threads that called wait() on the same object. The highest priority thread will run first in most of the situation, though not guaranteed. Other things are same as notify() method above.

Relative links:

Where wait() can be used?

synchronized

Relative links:

What is the keyword synchronized?

A piece of logic marked with synchronized becomes a synchronized block, allowing only one thread to execute at any given time.

Relative links:

What is the keyword volatile?

The volatile modifier guarantees that any thread that reads a field will see the most recently written value. In Java, each thread has a separate memory space known as working memory; this holds the values of different variables used for performing operations. After performing an operation, thread copies the updated value of the variable to the main memory, and from there other threads can read the latest value. Simply put, the volatile keyword marks a variable to always go to main memory, for both reads and writes, in the case of multiple threads accessing it.

Relative links:

How volatile is related to happens before?

To address the instruction reordering challenge, the Java volatile keyword gives a "happens-before" guarantee, in addition to the visibility guarantee. The happens-before guarantee guarantees that:

  • Reads from and writes to other variables cannot be reordered to occur after a write to a volatile variable, if the reads / writes originally occurred before the write to the volatile variable. The reads / writes before a write to a volatile variable are guaranteed to "happen before" the write to the volatile variable. Notice that it is still possible for e.g. reads / writes of other variables located after a write to a volatile to be reordered to occur before that write to the volatile. Just not the other way around. From after to before is allowed, but from before to after is not allowed.
  • Reads from and writes to other variables cannot be reordered to occur before a read of a volatile variable, if the reads / writes originally occurred after the read of the volatile variable. Notice that it is possible for reads of other variables that occur before the read of a volatile variable can be reordered to occur after the read of the volatile. Just not the other way around. From before to after is allowed, but from after to before is not allowed.

The above happens-before guarantee assures that the visibility guarantee of the volatile keyword are being enforced.

Relative links:

What are the concurrent collections?

  • ConcurrentHashMap
  • CopyOnWriteArrayList/CopyOnWriteArraySet
  • BlockingQueue
Relative links:

What is CopyOnWriteArrayList?

A thread-safe variant of ArrayList in which all mutative operations (add, set, and so on) are implemented by making a fresh copy of the underlying array.

Relative links:

What can you say about ConcurrentHashMap?

A hash table supporting full concurrency of retrievals and high expected concurrency for updates. This class obeys the same functional specification as Hashtable, and includes versions of methods corresponding to each method of Hashtable. However, even though all operations are thread-safe, retrieval operations do not entail locking, and there is not any support for locking the entire table in a way that prevents all access. This class is fully interoperable with Hashtable in programs that rely on its thread safety but not on its synchronization details.

Relative links:

What are the benefits of using ConcurrentHashMap over HashTable?

ConcurrentHashMap uses multiple buckets to store data. This avoids read locks and greatly improves performance over a HashTable. Both are thread safe, but there are obvious performance wins with ConcurrentHashMap. When you read from a ConcurrentHashMap using get(), there are no locks, contrary to the HashTable for which all operations are simply synchronized. HashTable was released in old versions of Java whereas ConcurrentHashMap is a java 5+ thing. HashMap is the best thing to use in a single threaded application.

Relative links:

What is the time complexity of get() operation in ConcurrentHashMap?

The get() operation in ConcurrentHashMap is O(1) average case, and O(log n) worst-case (since Java 8) when there are many hash collisions.

Relative links:

What is the synchronized access?

Synchronized blocks in Java are marked with the synchronized keyword. A synchronized block in Java is synchronized on some object. All synchronized blocks synchronized on the same object can only have one thread executing inside them at the same time. All other threads attempting to enter the synchronized block are blocked until the thread inside the synchronized block exits the block.

Relative links:

What is the monitor for non-static synchronized method?

When synchronizing a non static method, the monitor belongs to the instance.

Relative links:

What are possible ways for synchronization threads?

  • Volatile;
  • Synchronized;
  • Atomic Primitives;
  • Locks;
  • Concurrent Utilities (Semaphore, CyclicBarrier, CountDownLatch, Concurrent collections).
Relative links:

How to wait for finish of thread?

Thread has a method that does that for you join which will block until the thread has finished executing.

Relative links:

What is the result of this code?

public class StopThread {
    private static boolean stopRequested;
    public static void main(String[] args)
            throws InterruptedException {
        Thread backgroundThread = new Thread(new Runnable() {
            public void run() {
                int i = 0;
                while (!stopRequested)
                    i++;
            }
        });
        backgroundThread.start();
        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}

Ideally, the program should run for 1 second and after the “stopRequested” has become true, the “backgroundThread” should end, terminating the whole program.

But if you run the above on a computer with multiple cores, you will observe that the program keeps on executing without getting terminated. The problem occurs with the write operation on the “stopRequested” variable. There is no guarantee that the change of the value in “stopRequested” variable (from the main thread) becoming visible to the “backgroundThread” that we created. As the write operation to the “stopRequested” variable to true from the main method is invisible to the “backgroundThread”, it will go into an infinite loop.

As the main thread and our “backgroundThread” is running on two different cores inside the processor, the “stopRequested” will be loaded into the cache of the core that executes the “backgroundThread”. The main thread will keep the updated value of the “stopRequested” value in a cache of a different core. Since now the “stopRequested” value resides in two different caches, the updated value may not be visible to the “backgroundThread”.

Relative links:

Change the code for getting deadlock

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

public class Deadlock {
    private static final Object monitor1 = new Object();
    private static final Object monitor2 = new Object();

    public static void main(final String[] args) throws InterruptedException {
        List<Thread> threads = new ArrayList<>();
        threads.add(new Thread(Deadlock::handler1));
        threads.add(new Thread(Deadlock::handler2));

        Collections.shuffle(threads);

        threads.get(0).start();
        Thread.sleep(new Random().nextLong());
        threads.get(1).start();
    }

    private static void handler1() {
        synchronized (monitor1) {
            synchronized (monitor2) {
                System.out.println("Hello from handler1");
            }
        }
    }

    private static void handler2() {
        synchronized (monitor2) {
            synchronized (monitor1) {
                System.out.println("Hello from handler2");
            }
        }
    }
}

Possible solution - adding delays inside synchronized blocks, the example is in module deadlock.

What is the Difference between Future and CompletableFuture

Relative links:

How to handle unsuccessful results of CompletableFuture?

  • .exceptionally() - primary method for handling exceptions
  • .handle() - for handling both success and failure
Relative links:

Which executor implementation is used by default when you don't specify an executor for CompletableFuture methods like supplyAsync() or runAsync()?

When you don’t specify an executor, then Java’s ForkJoinPool‘s default executor is used.

Relative links:

What are the main methods in CompletableFuture that allow you to define and execute asynchronous tasks?

runAsync / supplyAsync are the main methods

Relative links:

How do AtomicVariables work?

Long story short: they use compare and swap algorithm.

Relative links:

How do you choose between using Virtual Threads (Project Loom) and Reactive Streams frameworks (like Reactor, Mutiny, Akka Streams) for concurrent programming in Java?

For new projects, consider virtual threads for simpler code unless you specifically need reactive features like backpressure or are using non-blocking libraries. For existing reactive applications, continue with reactive unless complexity is causing issues.

Relative links:

Home Page