Observability Done Right: Best Practices and Anti-Patterns for Effective System Monitoring

Image
  WHAT Observability is a concept that refers to the ability to gain insights into the behavior and performance of complex systems. In the context of software engineering, observability involves the collection, analysis, and visualization of data from software applications, infrastructure, and other components of a system. In the animal kingdom, observability plays a critical role in survival, allowing animals to monitor their surroundings, detect threats, and find food. Dolphins use echolocation to observe their surroundings. They emit high-frequency sounds that bounce off objects, allowing them to create a 3D map of their environment. Thanks for reading Knowledge Cafe! Subscribe for free to receive new posts and support my work. Subscribed WHY In today's era, architectures are becoming increasingly large, complex, and fast-paced due to the faster development and deployment of software by distributed teams with the help of DevOps, continuous delivery, and agile development methodo...

Java Thread Communication



Threads can be used to run more than one task at a time, you can keep one task from interfering with another task’s resources by using a lock (mutex) to synchronize the behavior of the two tasks. That is, if two tasks are stepping on each other over a shared resource (usually memory), you use a mutex to allow only one task at a time to access that resource.
In this article we will make tasks cooperate with each other, so that multiple tasks can work together to solve a problem. Now the issue is not about interfering with one another, but rather about working in unison, since portions of such problems must be solved before other portions can be solved.The key issue when tasks are cooperating is handshaking between those tasks.

We can use any of below methods for thread communication.

(1) Easiest, reliable, but inefficient way is to let thread-A periodically wake up and test a condition flag that thread-B will update. Sample code as below,


public class MyRunnable implements Runnable{
private volatile boolean flag;
@Override
public void run (){

    while (!flag) { // condition Flag is a volatile varable
        try { 
              Thread.sleep(1000);
       }catch(InterruptedException e){
       //Handle the exception
       }
    }
    // do something
  } 
}

2. Thread-A Wait and Thread-B Notify

Similar to method 1 but use build-in Object wait and notify mechanism instead of thread sleep. More efficient than method 1 but easy to mess up (avoid to use).

Every object has an intrinsic lock or monitor lock; the lock is automatically acquired when enter synchronized block and release when exit the block.

  • thread has to have the (lockObject) lock to call wait
  • the lock is release after calling wait (the thread suspended)
  • the thread wakes up if the lockobject is interrupted or notify is called

public void run() {
    synchronized (lockObject) {         // has to be synchronized
        while (!conditionFlag) {        // volatile flag
            try { lockObject.wait()}    // select lock object
            catch(InterruptedException e){}
        }
    }
}

In Thread B, call: lockObject.notifyAll() will trigger the event and wake up Thread A. (notifyAll is better than notify).

3. Thread-A Calls Thread-B Join

Thread A = new Thread(new Runnable() {
    public void run() {
        B.join();  // A will wait for Thread B to finish
        // do something
    }
}

Note join() is a method in Thread class, while wait() and notify() are in Object class.

4.Use Concurrency Utility Lock


It’s available from Java 1.5. It’s straight forward:
final Lock lock = new ReentrantLock();
...
lock.lock();  // block and wait until the lock is ready
try {
    // do something
}
finally {
    lock.unlock();
}

5.Use CountDownLatch

Available from Java 1.5 onwards.

A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.

CountDownLatch is initialized with a given count. The await methods block until the current count reaches zero due to invocations of the countDown() method, after which all waiting threads are released and any subsequent invocations of await return immediately. This is a one-shot phenomenon -- the count cannot be reset. If you need a version that resets the count, consider using a CyclicBarrier.

CountDownLatch is a versatile synchronization tool and can be used for a number of purposes. A CountDownLatch initialized with a count of one serves as a simple on/off latch, or gate: all threads invoking await wait at the gate until it is opened by a thread invoking countDown(). A CountDownLatch initialized to N can be used to make one thread wait until N threads have completed some action, or some action has been completed N times.

6. Semaphores

Semaphore is a word coined from ancient Greek words (sêma, phoros) meaning ‘sign, bearing/bearer’. Semaphore is a technique used to control access to common resource for competeing multiple processes. Semaphore maintains a counter which keeps track of the number of resources available. When a process requests access to resource, semaphore checks the variable count and if it is less than total count then grants access and subsequently reduces the available count.

final Semaphore sem = new Semaphore(2); // binary semaphore - two permit
...
public void run() {
    sem.acquire(); // wait until the semaphore is available
    // do something
    sem.release();
}

Thank you, hope you have enjoyed reading this article.

Popular posts from this blog

Chain of responsibility using Spring @Autowired List

Iterate Through a HashMap

Under the Hood: Understanding the Gossip Protocol in Apache Cassandra