Category: 05. Java Concurrency

https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQrxQmP248wdHdvx2H9qVDHQPHYpqA_XNsisA&s

  • AtomicInteger Class

    A java.util.concurrent.atomic.AtomicInteger class provides operations on underlying int value that can be read and written atomically, and also contains advanced atomic operations. AtomicInteger supports atomic operations on underlying int variable. It have get and set methods that work like reads and writes on volatile variables. That is, a set has a happens-before relationship with any subsequent get on the same variable. The atomic compareAndSet method also has these memory consistency features.

    AtomicInteger Methods

    Following is the list of important methods available in the AtomicInteger class.

    Sr.No.Method & Description
    1public int addAndGet(int delta)Atomically adds the given value to the current value.
    2public boolean compareAndSet(int expect, int update)Atomically sets the value to the given updated value if the current value is same as the expected value.
    3public int decrementAndGet()Atomically decrements by one the current value.
    4public double doubleValue()Returns the value of the specified number as a double.
    5public float floatValue()Returns the value of the specified number as a float.
    6public int get()Gets the current value.
    7public int getAndAdd(int delta)Atomiclly adds the given value to the current value.
    8public int getAndDecrement()Atomically decrements by one the current value.
    9public int getAndIncrement()Atomically increments by one the current value.
    10public int getAndSet(int newValue)Atomically sets to the given value and returns the old value.
    11public int incrementAndGet()Atomically increments by one the current value.
    12public int intValue()Returns the value of the specified number as an int.
    13public void lazySet(int newValue)Eventually sets to the given value.
    14public long longValue()Returns the value of the specified number as a long.
    15public void set(int newValue)Sets to the given value.
    16public String toString()Returns the String representation of the current value.
    17public boolean weakCompareAndSet(int expect, int update)Atomically sets the value to the given updated value if the current value is same as the expected value.

    Example

    The following TestThread program shows a unsafe implementation of counter in thread based environment.

    public class TestThread {
    
       static class Counter {
    
      private int c = 0;
      public void increment() {
         c++;
      }
      public int value() {
         return c;
      }
    } public static void main(final String[] arguments) throws InterruptedException {
      final Counter counter = new Counter();
      
      //1000 threads
      for(int i = 0; i < 1000 ; i++) {
         
         new Thread(new Runnable() {
            
            public void run() {
               counter.increment();
            }
         }).start(); 
      }  
      Thread.sleep(6000);
      System.out.println("Final number (should be 1000): " + counter.value());
    } }

    This may produce the following result depending upon computer’s speed and thread interleaving.

    Output

    Final number (should be 1000): 1000
    

    Example

    The following TestThread program shows a safe implementation of counter using AtomicInteger in thread based environment.

    import java.util.concurrent.atomic.AtomicInteger;
    
    public class TestThread {
    
       static class Counter {
    
      private AtomicInteger c = new AtomicInteger(0);
      public void increment() {
         c.getAndIncrement();
      }
      public int value() {
         return c.get();
      }
    } public static void main(final String[] arguments) throws InterruptedException {
      final Counter counter = new Counter();
      
      //1000 threads
      for(int i = 0; i < 1000 ; i++) {
         new Thread(new Runnable() {
            public void run() {
               counter.increment();
            }
         }).start(); 
      }  
      Thread.sleep(6000);
      System.out.println("Final number (should be 1000): " + counter.value());
    } }

    This will produce the following result.

    Output

    Final number (should be 1000): 1000
    
  • Condition Interface

    A java.util.concurrent.locks.Condition interface provides a thread ability to suspend its execution, until the given condition is true. A Condition object is necessarily bound to a Lock and to be obtained using the newCondition() method.

    Condition Methods

    Following is the list of important methods available in the Condition class.

    Sr.No.Method & Description
    1public void await()Causes the current thread to wait until it is signalled or interrupted.
    2public boolean await(long time, TimeUnit unit)Causes the current thread to wait until it is signalled or interrupted, or the specified waiting time elapses.
    3public long awaitNanos(long nanosTimeout)Causes the current thread to wait until it is signalled or interrupted, or the specified waiting time elapses.
    4public long awaitUninterruptibly()Causes the current thread to wait until it is signalled.
    5public long awaitUntil()Causes the current thread to wait until it is signalled or interrupted, or the specified deadline elapses.
    6public void signal()Wakes up one waiting thread.
    7public void signalAll()Wakes up all waiting threads.

    Example

    The following TestThread program demonstrates these methods of the Condition interface. Here we’ve used signal() to notify and await() to suspend the thread.

    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class TestThread {
    
       public static void main(String[] args) throws InterruptedException {
    
      ItemQueue itemQueue = new ItemQueue(10);
      //Create a producer and a consumer.
      Thread producer = new Producer(itemQueue);
      Thread consumer = new Consumer(itemQueue);
      //Start both threads.
      producer.start();
      consumer.start();
      //Wait for both threads to terminate.
      producer.join();
      consumer.join();
    } static class ItemQueue {
      private Object[] items = null;
      private int current = 0;
      private int placeIndex = 0;
      private int removeIndex = 0;
      private final Lock lock;
      private final Condition isEmpty;
      private final Condition isFull;
      public ItemQueue(int capacity) {
         this.items = new Object[capacity];
         lock = new ReentrantLock();
         isEmpty = lock.newCondition();
         isFull = lock.newCondition();
      }
      public void add(Object item) throws InterruptedException {
         lock.lock();
         while(current >= items.length)
            isFull.await();
         items[placeIndex] = item;
         placeIndex = (placeIndex + 1) % items.length;
         ++current;
         //Notify the consumer that there is data available.
         isEmpty.signal();
         lock.unlock();
      }
      public Object remove() throws InterruptedException {
         Object item = null;
         lock.lock();
         while(current <= 0) {
            isEmpty.await();
         }
         item = items[removeIndex];
         removeIndex = (removeIndex + 1) % items.length;
         --current;
         //Notify the producer that there is space available.
         isFull.signal();
         lock.unlock();
         return item;
      }
      public boolean isEmpty() {
         return (items.length == 0);
      }
    } static class Producer extends Thread {
      private final ItemQueue queue;
      
      public Producer(ItemQueue queue) {
         this.queue = queue;
      }
      @Override
      public void run() {
         String[] numbers =
            {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"};
         try {
            
            for(String number: numbers) {
               System.out.println("[Producer]: " + number);
            }
            queue.add(null);
         } catch (InterruptedException ex) {
            ex.printStackTrace();
         } 
      }
    } static class Consumer extends Thread {
      private final ItemQueue queue;
      
      public Consumer(ItemQueue queue) {
         this.queue = queue;
      }
      @Override
      public void run() {
         
         try {
            
            do {
               Object number = queue.remove();
               System.out.println("[Consumer]: " + number);
               if(number == null) {
                  return;
               }
            } while(!queue.isEmpty());
         } catch (InterruptedException ex) {
            ex.printStackTrace();
         }
      }
    } }

    This will produce the following result.

    Output

    [Producer]: 1
    [Producer]: 2
    [Producer]: 3
    [Producer]: 4
    [Producer]: 5
    [Producer]: 6
    [Producer]: 7
    [Producer]: 8
    [Producer]: 9
    [Producer]: 10
    [Producer]: 11
    [Producer]: 12
    [Consumer]: null
    
  • ReadWriteLock Interface

    A java.util.concurrent.locks.ReadWriteLock interface allows multiple threads to read at a time but only one thread can write at a time.

    • Read Lock − If no thread has locked the ReadWriteLock for writing then multiple thread can access the read lock.
    • Write Lock − If no thread is reading or writing, then one thread can access the write lock.

    Lock Methods

    Following is the list of important methods available in the Lock class.

    Sr.No.Method & Description
    1public Lock readLock()Returns the lock used for reading.
    2public Lock writeLock()Returns the lock used for writing.

    Example

    The following TestThread program demonstrates these methods of the ReadWriteLock interface. Here we’ve used readlock() to acquire the read-lock and writeLock() to acquire the write-lock.

    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    public class TestThread {
       private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
       private static String message = "a";
    
       public static void main(String[] args) throws InterruptedException {
    
      Thread t1 = new Thread(new WriterA());
      t1.setName("Writer A");
      
      Thread t2 = new Thread(new WriterB());
      t2.setName("Writer B");
      
      Thread t3 = new Thread(new Reader());
      t3.setName("Reader");
      t1.start();
      t2.start();
      t3.start();
      t1.join();
      t2.join();
      t3.join();
    } static class Reader implements Runnable {
      public void run() {
         
         if(lock.isWriteLocked()) {
            System.out.println("Write Lock Present.");
         }
         lock.readLock().lock();
         try {
            Long duration = (long) (Math.random() * 10000);
            System.out.println(Thread.currentThread().getName() 
               + "  Time Taken " + (duration / 1000) + " seconds.");
            Thread.sleep(duration);
         } catch (InterruptedException e) {
            e.printStackTrace();
         } finally {
            System.out.println(Thread.currentThread().getName() +": "+ message );
            lock.readLock().unlock();
         }
      }
    } static class WriterA implements Runnable {
      public void run() {
         lock.writeLock().lock();
         
         try {
            Long duration = (long) (Math.random() * 10000);
            System.out.println(Thread.currentThread().getName() 
               + "  Time Taken " + (duration / 1000) + " seconds.");
            Thread.sleep(duration);
         } catch (InterruptedException e) {
            e.printStackTrace();
         } finally {
            message = message.concat("a");
            lock.writeLock().unlock();
         }
      }
    } static class WriterB implements Runnable {
      public void run() {
         lock.writeLock().lock();
         
         try {
            Long duration = (long) (Math.random() * 10000);
            System.out.println(Thread.currentThread().getName() 
               + "  Time Taken " + (duration / 1000) + " seconds.");
            Thread.sleep(duration);
         } catch (InterruptedException e) {
            e.printStackTrace();
         } finally {
            message = message.concat("b");
            lock.writeLock().unlock();
         }
      }
    } }

    This will produce the following result.

    Output

    Writer A  Time Taken 6 seconds.
    Write Lock Present.
    Writer B  Time Taken 2 seconds.
    Reader  Time Taken 0 seconds.
    Reader: aab
    
  • Lock Interface

    A java.util.concurrent.locks.Lock interface is used to as a thread synchronization mechanism similar to synchronized blocks. New Locking mechanism is more flexible and provides more options than a synchronized block. Main differences between a Lock and a synchronized block are following −

    • Guarantee of sequence − Synchronized block does not provide any guarantee of sequence in which waiting thread will be given access. Lock interface handles it.
    • No timeout − Synchronized block has no option of timeout if lock is not granted. Lock interface provides such option.
    • Single method − Synchronized block must be fully contained within a single method whereas a lock interface’s methods lock() and unlock() can be called in different methods.

    Lock Methods

    Following is the list of important methods available in the Lock class.

    Sr.No.Method & Description
    1public void lock()Acquires the lock.
    2public void lockInterruptibly()Acquires the lock unless the current thread is interrupted.
    3public Condition newCondition()Returns a new Condition instance that is bound to this Lock instance.
    4public boolean tryLock()Acquires the lock only if it is free at the time of invocation.
    5public boolean tryLock(long time, TimeUnit unit)Acquires the lock if it is free within the given waiting time and the current thread has not been interrupted.
    6public void unlock()Releases the lock.

    Example

    The following TestThread program demonstrates some of these methods of the Lock interface. Here we’ve used lock() to acquire the lock and unlock() to release the lock.

    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    class PrintDemo {
       private final Lock queueLock = new ReentrantLock();
    
       public void print() {
    
      queueLock.lock();
      try {
         Long duration = (long) (Math.random() * 10000);
         System.out.println(Thread.currentThread().getName() 
            + "  Time Taken " + (duration / 1000) + " seconds.");
         Thread.sleep(duration);
      } catch (InterruptedException e) {
         e.printStackTrace();
      } finally {
         System.out.printf(
            "%s printed the document successfully.\n", Thread.currentThread().getName());
         queueLock.unlock();
      }
    } } class ThreadDemo extends Thread { PrintDemo printDemo; ThreadDemo(String name, PrintDemo printDemo) {
      super(name);
      this.printDemo = printDemo;
    } @Override public void run() {
      System.out.printf(
         "%s starts printing a document\n", Thread.currentThread().getName());
      printDemo.print();
    } } public class TestThread { public static void main(String args[]) {
      PrintDemo PD = new PrintDemo();
      ThreadDemo t1 = new ThreadDemo("Thread - 1 ", PD);
      ThreadDemo t2 = new ThreadDemo("Thread - 2 ", PD);
      ThreadDemo t3 = new ThreadDemo("Thread - 3 ", PD);
      ThreadDemo t4 = new ThreadDemo("Thread - 4 ", PD);
      t1.start();
      t2.start();
      t3.start();
      t4.start();
    } }

    This will produce the following result.

    Output

    Thread - 1  starts printing a document
    Thread - 4  starts printing a document
    Thread - 3  starts printing a document
    Thread - 2  starts printing a document
    Thread - 1   Time Taken 4 seconds.
    Thread - 1  printed the document successfully.
    Thread - 4   Time Taken 3 seconds.
    Thread - 4  printed the document successfully.
    Thread - 3   Time Taken 5 seconds.
    Thread - 3  printed the document successfully.
    Thread - 2   Time Taken 4 seconds.
    Thread - 2  printed the document successfully.
    

    We’ve use ReentrantLock class as an implementation of Lock interface here. ReentrantLock class allows a thread to lock a method even if it already have the lock on other method.

  • ThreadLocalRandom Class

    A java.util.concurrent.ThreadLocalRandom is a utility class introduced from jdk 1.7 onwards and is useful when multiple threads or ForkJoinTasks are required to generate random numbers. It improves performance and have less contention than Math.random() method.

    ThreadLocalRandom Methods

    Following is the list of important methods available in the ThreadLocalRandom class.

    Sr.No.Method & Description
    1public static ThreadLocalRandom current()Returns the current thread’s ThreadLocalRandom.
    2protected int next(int bits)Generates the next pseudorandom number.
    3public double nextDouble(double n)Returns a pseudorandom, uniformly distributed double value between 0 (inclusive) and the specified value (exclusive).
    4public double nextDouble(double least, double bound)Returns a pseudorandom, uniformly distributed value between the given least value (inclusive) and bound (exclusive).
    5public int nextInt(int least, int bound)Returns a pseudorandom, uniformly distributed value between the given least value (inclusive) and bound (exclusive).
    6public long nextLong(long n)Returns a pseudorandom, uniformly distributed value between 0 (inclusive) and the specified value (exclusive).
    7public long nextLong(long least, long bound)Returns a pseudorandom, uniformly distributed value between the given least value (inclusive) and bound (exclusive).
    8public void setSeed(long seed)Throws UnsupportedOperationException.

    Example

    The following TestThread program demonstrates some of these methods of the Lock interface. Here we’ve used lock() to acquire the lock and unlock() to release the lock.

    import java.util.Random;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    import java.util.concurrent.ThreadLocalRandom;
    
    public class TestThread {
      
       public static void main(final String[] arguments) {
    
      System.out.println("Random Integer: " + new Random().nextInt());  
      System.out.println("Seeded Random Integer: " + new Random(15).nextInt());  
      System.out.println(
         "Thread Local Random Integer: " + ThreadLocalRandom.current().nextInt());
      
      final ThreadLocalRandom random = ThreadLocalRandom.current();  
      random.setSeed(15); //exception will come as seeding is not allowed in ThreadLocalRandom.
      System.out.println("Seeded Thread Local Random Integer: " + random.nextInt());  
    } }

    This will produce the following result.

    Output

    Random Integer: 1566889198
    Seeded Random Integer: -1159716814
    Thread Local Random Integer: 358693993
    Exception in thread "main" java.lang.UnsupportedOperationException
    
        at java.util.concurrent.ThreadLocalRandom.setSeed(Unknown Source)
        at TestThread.main(TestThread.java:21)

    Here we’ve used ThreadLocalRandom and Random classes to get random numbers.

  • ThreadLocal Class

    The ThreadLocal class is used to create thread local variables which can only be read and written by the same thread. For example, if two threads are accessing code having reference to same threadLocal variable then each thread will not see any modification to threadLocal variable done by other thread.

    ThreadLocal Methods

    Following is the list of important methods available in the ThreadLocal class.

    Sr.No.Method & Description
    1public T get()Returns the value in the current thread’s copy of this thread-local variable.
    2protected T initialValue()Returns the current thread’s “initial value” for this thread-local variable.
    3public void remove()Removes the current thread’s value for this thread-local variable.
    4public void set(T value)Sets the current thread’s copy of this thread-local variable to the specified value.

    Example

    The following TestThread program demonstrates some of these methods of the ThreadLocal class. Here we’ve used two counter variable, one is normal variable and another one is ThreadLocal.

    class RunnableDemo implements Runnable {
       int counter;
       ThreadLocal<Integer> threadLocalCounter = new ThreadLocal<Integer>();
    
       public void run() {     
    
      counter++;
      if(threadLocalCounter.get() != null) {
         threadLocalCounter.set(threadLocalCounter.get().intValue() + 1);
      } else {
         threadLocalCounter.set(0);
      }
      System.out.println("Counter: " + counter);
      System.out.println("threadLocalCounter: " + threadLocalCounter.get());
    } } public class TestThread { public static void main(String args[]) {
      RunnableDemo commonInstance = new RunnableDemo();
      Thread t1 = new Thread(commonInstance);
      Thread t2 = new Thread(commonInstance);
      Thread t3 = new Thread(commonInstance);
      Thread t4 = new Thread(commonInstance);
      t1.start();
      t2.start();
      t3.start();
      t4.start();
      // wait for threads to end
      try {
         t1.join();
         t2.join();
         t3.join();
         t4.join();
      } catch (Exception e) {
         System.out.println("Interrupted");
      }
    } }

    This will produce the following result.

    Output

    Counter: 1
    threadLocalCounter: 0
    Counter: 2
    threadLocalCounter: 0
    Counter: 3
    threadLocalCounter: 0
    Counter: 4
    threadLocalCounter: 0
    

    You can see the value of counter is increased by each thread, but threadLocalCounter remains 0 for each thread.

  • Deadlock

    Deadlock describes a situation where two or more threads are blocked forever, waiting for each other. Deadlock occurs when multiple threads need the same locks but obtain them in different order. A Java multithreaded program may suffer from the deadlock condition because the synchronized keyword causes the executing thread to block while waiting for the lock, or monitor, associated with the specified object. Here is an example.

    Example

    public class TestThread {
       public static Object Lock1 = new Object();
       public static Object Lock2 = new Object();
       
       public static void main(String args[]) {
    
      ThreadDemo1 T1 = new ThreadDemo1();
      ThreadDemo2 T2 = new ThreadDemo2();
      T1.start();
      T2.start();
    } private static class ThreadDemo1 extends Thread {
      public void run() {
      
         synchronized (Lock1) {
            System.out.println("Thread 1: Holding lock 1...");
            try {
               Thread.sleep(10);
            } catch (InterruptedException e) {}
            System.out.println("Thread 1: Waiting for lock 2...");
            synchronized (Lock2) {
               System.out.println("Thread 1: Holding lock 1 &amp; 2...");
            }
         }
      }
    } private static class ThreadDemo2 extends Thread {
      public void run() {
      
         synchronized (Lock2) {
            System.out.println("Thread 2: Holding lock 2...");
            
            try {
               Thread.sleep(10);
            } catch (InterruptedException e) {}
            System.out.println("Thread 2: Waiting for lock 1...");
            
            synchronized (Lock1) {
               System.out.println("Thread 2: Holding lock 1 &amp; 2...");
            }
         }
      }
    } }

    When you compile and execute the above program, you find a deadlock situation and following is the output produced by the program −

    Output

    Thread 1: Holding lock 1...
    Thread 2: Holding lock 2...
    Thread 1: Waiting for lock 2...
    Thread 2: Waiting for lock 1...
    

    The above program will hang forever because neither of the threads in position to proceed and waiting for each other to release the lock, so you can come out of the program by pressing CTRL+C.

    Deadlock Solution Example

    Let’s change the order of the lock and run of the same program to see if both the threads still wait for each other −

    Example

    public class TestThread {
       public static Object Lock1 = new Object();
       public static Object Lock2 = new Object();
       
       public static void main(String args[]) {
    
      ThreadDemo1 T1 = new ThreadDemo1();
      ThreadDemo2 T2 = new ThreadDemo2();
      T1.start();
      T2.start();
    } private static class ThreadDemo1 extends Thread {
      public void run() {
         
         synchronized (Lock1) {
            System.out.println("Thread 1: Holding lock 1...");
            
            try {
               Thread.sleep(10);
            } catch (InterruptedException e) {}
            System.out.println("Thread 1: Waiting for lock 2...");
            synchronized (Lock2) {
               System.out.println("Thread 1: Holding lock 1 &amp; 2...");
            }
         }
      }
    } private static class ThreadDemo2 extends Thread {
      
      public void run() {
         
         synchronized (Lock1) {
            System.out.println("Thread 2: Holding lock 1...");
           
            try {
               Thread.sleep(10);
            } catch (InterruptedException e) {}
            System.out.println("Thread 2: Waiting for lock 2...");
            synchronized (Lock2) {
               System.out.println("Thread 2: Holding lock 1 &amp; 2...");
            }
         }
      }
    } }

    So just changing the order of the locks prevent the program in going into a deadlock situation and completes with the following result −

    Output

    Thread 1: Holding lock 1...
    Thread 1: Waiting for lock 2...
    Thread 1: Holding lock 1 & 2...
    Thread 2: Holding lock 1...
    Thread 2: Waiting for lock 2...
    Thread 2: Holding lock 1 & 2...
    

    The above example is to just make the concept clear, however, it is a complex concept and you should deep dive into it before you develop your applications to deal with deadlock situations.

  • Synchronization

    Multithreading Example with Synchronization

    Here is the same example which prints counter value in sequence and every time we run it, it produces the same result.

    Example

    class PrintDemo {
       
       public void printCount() {
    
      
      try {
         
         for(int i = 5; i &gt; 0; i--) {
            System.out.println("Counter   ---   "  + i );
         }
      } catch (Exception e) {
         System.out.println("Thread  interrupted.");
      }
    } } class ThreadDemo extends Thread { private Thread t; private String threadName; PrintDemo PD; ThreadDemo(String name, PrintDemo pd) {
      threadName = name;
      PD = pd;
    } public void run() {
      
      synchronized(PD) {
         PD.printCount();
      }
      System.out.println("Thread " +  threadName + " exiting.");
    } public void start () {
      System.out.println("Starting " +  threadName );
      
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
    } } public class TestThread { public static void main(String args[]) {
      PrintDemo PD = new PrintDemo();
      ThreadDemo T1 = new ThreadDemo("Thread - 1 ", PD);
      ThreadDemo T2 = new ThreadDemo("Thread - 2 ", PD);
      T1.start();
      T2.start();
      // wait for threads to end
      try {
         T1.join();
         T2.join();
      } catch (Exception e) {
         System.out.println("Interrupted");
      }
    } }

    This produces the same result every time you run this program −

    Output

    Starting Thread - 1
    Starting Thread - 2
    Counter   ---   5
    Counter   ---   4
    Counter   ---   3
    Counter   ---   2
    Counter   ---   1
    Thread Thread - 1  exiting.
    Counter   ---   5
    Counter   ---   4
    Counter   ---   3
    Counter   ---   2
    Counter   ---   1
    Thread Thread - 2  exiting.
    
  • Interthread Communication

    If you are aware of interprocess communication then it will be easy for you to understand interthread communication. Interthread communication is important when you develop an application where two or more threads exchange some information.

    There are three simple methods and a little trick which makes thread communication possible. All the three methods are listed below −

    Sr.No.Method & Description
    1public void wait()Causes the current thread to wait until another thread invokes the notify().
    2public void notify()Wakes up a single thread that is waiting on this object’s monitor.
    3public void notifyAll()Wakes up all the threads that called wait( ) on the same object.

    These methods have been implemented as final methods in Object, so they are available in all the classes. All three methods can be called only from within a synchronized context.

    Example

    This examples shows how two threads can communicate using wait() and notify() method. You can create a complex system using the same concept.

    class Chat {
       boolean flag = false;
    
       public synchronized void Question(String msg) {
    
    
      if (flag) {
         
         try {
            wait();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
      System.out.println(msg);
      flag = true;
      notify();
    } public synchronized void Answer(String msg) {
      if (!flag) {
         
         try {
            wait();
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
      System.out.println(msg);
      flag = false;
      notify();
    } } class T1 implements Runnable { Chat m; String[] s1 = { "Hi", "How are you ?", "I am also doing fine!" }; public T1(Chat m1) {
      this.m = m1;
      new Thread(this, "Question").start();
    } public void run() {
      for (int i = 0; i &lt; s1.length; i++) {
         m.Question(s1&#91;i]);
      }
    } } class T2 implements Runnable { Chat m; String[] s2 = { "Hi", "I am good, what about you?", "Great!" }; public T2(Chat m2) {
      this.m = m2;
      new Thread(this, "Answer").start();
    } public void run() {
      for (int i = 0; i &lt; s2.length; i++) {
         m.Answer(s2&#91;i]);
      }
    } } public class TestThread { public static void main(String[] args) {
      Chat m = new Chat();
      new T1(m);
      new T2(m);
    } }

    When the above program is complied and executed, it produces the following result −

    Output

    Hi
    Hi
    How are you ?
    I am good, what about you?
    I am also doing fine!
    Great!
    

    Above example has been taken and then modified from [https://stackoverflow.com/questions/2170520/inter-thread-communication-in-java]

  • Major Operations

    Core Java provides complete control over multithreaded program. You can develop a multithreaded program which can be suspended, resumed, or stopped completely based on your requirements. There are various static methods which you can use on thread objects to control their behavior. Following table lists down those methods −

    Sr.No.Method & Description
    1public void suspend()This method puts a thread in the suspended state and can be resumed using resume() method.
    2public void stop()This method stops a thread completely.
    3public void resume()This method resumes a thread, which was suspended using suspend() method.
    4public void wait()Causes the current thread to wait until another thread invokes the notify().
    5public void notify()Wakes up a single thread that is waiting on this object’s monitor.

    Be aware that the latest versions of Java has deprecated the usage of suspend( ), resume( ), and stop( ) methods and so you need to use available alternatives.

    Example

    class RunnableDemo implements Runnable {
       public Thread t;
       private String threadName;
       boolean suspended = false;
    
       RunnableDemo(String name) {
    
      threadName = name;
      System.out.println("Creating " +  threadName );
    } public void run() {
      System.out.println("Running " +  threadName );
      try {
         
         for(int i = 10; i &gt; 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            // Let the thread sleep for a while.
            Thread.sleep(300);
            synchronized(this) {
               
               while(suspended) {
                  wait();
               }
            }
         }
      } catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      System.out.println("Thread " +  threadName + " exiting.");
    } public void start () {
      System.out.println("Starting " +  threadName );
      
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
    } void suspend() {
      suspended = true;
    } synchronized void resume() {
      suspended = false;
      notify();
    } } public class TestThread { public static void main(String args[]) {
      RunnableDemo R1 = new RunnableDemo("Thread-1");
      R1.start();
      RunnableDemo R2 = new RunnableDemo("Thread-2");
      R2.start();
      try {
         Thread.sleep(1000);
         R1.suspend();
         System.out.println("Suspending First Thread");
         Thread.sleep(1000);
         R1.resume();
         System.out.println("Resuming First Thread");
         
         R2.suspend();
         System.out.println("Suspending thread Two");
         Thread.sleep(1000);
         R2.resume();
         System.out.println("Resuming thread Two");
      } catch (InterruptedException e) {
         System.out.println("Main thread Interrupted");
      } try {
         System.out.println("Waiting for threads to finish.");
         R1.t.join();
         R2.t.join();
      } catch (InterruptedException e) {
         System.out.println("Main thread Interrupted");
      }
      System.out.println("Main thread exiting.");
    } }

    The above program produces the following output −

    Output

    Creating Thread-1
    Starting Thread-1
    Creating Thread-2
    Starting Thread-2
    Running Thread-1
    Thread: Thread-1, 10
    Running Thread-2
    Thread: Thread-2, 10
    Thread: Thread-1, 9
    Thread: Thread-2, 9
    Thread: Thread-1, 8
    Thread: Thread-2, 8
    Thread: Thread-1, 7
    Thread: Thread-2, 7
    Suspending First Thread
    Thread: Thread-2, 6
    Thread: Thread-2, 5
    Thread: Thread-2, 4
    Resuming First Thread
    Suspending thread Two
    Thread: Thread-1, 6
    Thread: Thread-1, 5
    Thread: Thread-1, 4
    Thread: Thread-1, 3
    Resuming thread Two
    Thread: Thread-2, 3
    Waiting for threads to finish.
    Thread: Thread-1, 2
    Thread: Thread-2, 2
    Thread: Thread-1, 1
    Thread: Thread-2, 1
    Thread Thread-1 exiting.
    Thread Thread-2 exiting.
    Main thread exiting.