Java - Concurrency Basics



Synchronized Method | Synchronized Block | Synchronized Static Method | Interleave | Data Race | Forward Substitution | Liveness | Starvation | Livelock | Deadlock | Lock Convoy | Priority Inversion | Atomic Access | volatile | Thrashing | CPU Stall |

The only way for a user to create a thread is to create an object of Thread class.
A thread will start when the start() method is invoked.

Threads


Threads are sometimes called lightweight processes. Both processes and threads provide an execution environment, but creating a new thread requires fewer resources than creating a new process.

Threads exist within a process — every process has at least one. Threads share the process's resources, including memory and open files. This makes for efficient, but potentially problematic, communication.

Threads may be supported by having many hardware processors, by time-slicing a single hardware processor, or by time-slicing many hardware processors.

Runnable object can subclass a class other than Thread


Every thread has a priority. Threads with higher priority are executed in preference to threads with lower priority. Each thread may or may not also be marked as a daemon. When code running in some thread creates a new Thread object, the new thread has its priority initially set equal to the priority of the creating thread, and is a daemon thread if and only if the creating thread is a daemon.

Interruption


Interruption in Java is not pre-emptive. Put another way both threads have to cooperate in order to process the interrupt properly. If the target thread does not poll the interrupted status the interrupt is effectively ignored.


thread.interrupt() [ public void interrupt() ]

Sets the interrupt status of the Thread.

if(this != currentThread()) {
    this.checkAccess(); // This can throw SecurityException if access is not allowed
}

Exceptional Flow (interrupt status is NOT set)

If the Thread is blocked [ on wait(), join(), sleep() ] its interrupt status will be cleared and the thread will receive InterruptedException [ thrown by the JVM ?? ]

Any method that exits by throwing an InterruptedException clears interrupt status when it does so.


Thread.interrupted()
 [ public static boolean interrupted() ]

Tests whether the current thread has been interrupted. The interrupted status of the thread is cleared by this method.


thread.isInterrupted()
 [ public boolean isInterrupted() ]

Tests whether the current thread has been interrupted. The interrupted status of the thread is unaffected by this method.




Thread.sleep() [ public static void sleep(long) ]

Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers. The thread does not lose ownership of any monitors.


thread.stop()

This method is inherently unsafe. Stopping a thread with Thread.stop causes it to unlock all of the monitors that it has locked (as a natural consequence of the unchecked ThreadDeath exception propagating up the stack). If any of the objects previously protected by these monitors were in an inconsistent state, the damaged objects become visible to other threads, potentially resulting in arbitrary behavior. Many uses of stop should be replaced by code that simply modifies some variable to indicate that the target thread should stop running.

Object.wait()

Object.notify()

Object.notifyAll()

wait(), notify() & notifyAll() should always be called from synchronized block.
This is required as these methods are called while checking some condition.
The condition check and one of above method call is not atomic.
This could cause a race condition resulting in Lost Notification.

these sleep times are not guaranteed to be precise, because they are limited by the facilities provided by the underlying OS



Disadvantages of Locking 

Starvation - When a thread is unable to make progress due to waiting for access to shared resources. One thread is impacted.

A situation where a thread is unable to gain regular access to shared resources and is unable to make progress. This happens when shared resources are made unavailable for long periods by "greedy" threads.

Live Lock - As with deadlock, livelocked threads are unable to make further progress. However, the threads are not blocked — they are simply too busy responding to each other to resume work. A thread often acts in response to the action of another thread. If the other thread's action is also a response to the action of another thread, then livelock may result.

Dead Lock - If a process is unable to change its state indefinitely because the resources requested by it are being used by another waiting process, then the system is said to be in a deadlock.

A deadlock situation can arise if all of the following conditions hold simultaneously in a system
  • Mutual Exclusion
  • Hold (one resource) and wait (to get lock on other)
  • No Preemption (by the System). Only voluntarily release
  • Circular Wait

Lock Convoy - A lock convoy occurs when multiple threads of equal priority contend repeatedly for the same lock. Unlike deadlock and livelock situations, the threads in a lock convoy do progress; however, each time a thread attempts to acquire the lock and fails, it relinquishes the remainder of its scheduling quantum and forces a context switch. The overhead of repeated context switches and underutilization of scheduling quanta degrade overall performance.

Priority Inversion - Low priority threads hold locks that high priority threads are waiting for.

Thrashing -  Thrashing is a state in which the CPU performs 'productive' work less and 'swapping' more. Another example of this is cache thrashing, where main memory is accessed in a pattern that leads to multiple main memory locations competing for the same cache lines, resulting in excessive cache misses



Creating Immutable Objects

  • Make all fields private & final
  • Dont provide setters
  • Declare class as final to avoid subclassing OR make constructor private and use factory methods to create instances.
  • If instance fields refer to mutable objects
    • don't provide methods to change these references
    • don't share references to mutable objects
    • never store references to external mutable objects provided to the constructor, if need to store create a copy and store the copy.
    • when returning return a copy instead of original mutable object. 



Implementation of Mutual Exclusion (synchronised blocks / variables )


Mutex is made of two major parts (oversimplifying): (1) a flag indicating whether the mutex is locked or not and (2) wait queue.
Change of the flag is just few instructions and normally done without system call. If mutex is locked, syscall will happen to add the calling thread into wait queue and start the waiting. Unlocking, if the wait queue is empty, is cheap but otherwise needs a syscall to wake up one of the waiting processes. (On some systems cheap/fast syscalls are used to implement the mutexes, they become slow (normal) system calls only in case of contention.)
Locking unlocked mutex is really cheap. Unlocking mutex w/o contention is cheap too.

https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

Why should a Thread wait in while condition loop - Spurious Wakeups - So as to avoid these (details further below).

Why notifyAll() is preferred to notify() - Lost Notifications - Doug Lea brings up an interesting point in his famous book: if a notify() and Thread.interrupt() happen at the same time, the notify might actually get lost. If this can happen and has dramatic implications notifyAll() is a safer choice even though you pay the price of overhead (waking too many threads most of the time).




No comments:

Post a Comment