Multithreading problems

Problems can arise when working with threads.

dead lock

The dead lock is situation when threads waits each other inside the synchronized blocks.

In following code both threads invoke the synchronized method bow(). This means, first thread synchronized on alphonse object. And second thread synchronized on gaston object.

But inside bow() we call other method, that synchronized on other object, which already busy by other thread. Thus in our case alphonse wait gaston, and gaston wait alphonse.

public class Deadlock {

    static class Friend {
        private final String name;
        
        public Friend(String name) { this.name = name; }
        public String getName() { return this.name; }
        
        public synchronized void bow(Friend bower) {
            System.out.format("%s: %s"
                + "  has bowed to me!%n", 
                this.name, bower.getName());
                
           bower.bowBack(this);
        }
        
        public synchronized void bowBack(Friend bower) {
            System.out.format("%s: %s"
                + " has bowed back to me!%n",
                this.name, bower.getName());
        }
    }

    public static void main(String[] args) {
        final Friend alphonse = new Friend("Alphonse");
        final Friend gaston = new Friend("Gaston");
        
        new Thread(() -> alphonse.bow(gaston)).start();
        new Thread(() -> gaston.bow(alphonse)).start();
    }
}

livelock

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. 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.

starvation

Starvation describes 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. For example, suppose an object provides a synchronized method that often takes a long time to return. If one thread invokes this method frequently, other threads that also need frequent synchronized access to the same object will often be blocked.

When the hungry thread is the gui thread, the user will see application lags.

memory consistency error

The memory consistency error is a situation where one thread does not see the changes made by another thread. You can avoid this by using

  1. synchronization
  2. using constant data or immutable objects
  3. using volatile data
public class MemoryConsistencyError{
    static int sCounter = 0;

    static void inc() {
        for (int j = 0; j < 100; j++) {
            sCounter+=1;
            System.out.println("After increment is " + sCounter);
        }
    }

    static void dec() {
        for (int j = 0; j < 100; j++) {
            sCounter-=1;
            System.out.println("After decrement is " + sCounter);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(()->inc()).start();
        new Thread(()->dec()).start();
        // in result you can see logs like
        // After increment is 23
        // After decrement is 11 // but you could expect 22
    }
}

thread interference error

The thread interference error is situation when two or more threads performing different operations on the same data interleave with each other and create inconsistent data in the memory.