Thursday, October 4, 2012

Synchronization and Thread Safety in Hashtable, Hashmap, ConcurerntHashMap

Coming across blog entry "Who said Java Hashtable is thread safe?", I decided to run the code.  He's right in saying that Hashtable isn't thread safe, though, I don't think such a claim was ever officially made.  Maybe if there is allusion among some that it is, then I suppose it needs dispelling.  Thread safety is a design challenge.  Fully synchronized classes, and even completely thread-safe classes can be used inefficiently, or worse, incorrectly, in a concurrent system.

Here are three examples of thread unsafe usage, followed by three examples of safe usage:

1. Unsafe

package unsafe.demo;

import java.util.HashMap;
import java.util.Map;

import unsafe.threads.Unsafe_Hashmap_NoSynchronized_NotUsingContainsKey;

public class DemoThreadUnsafe {
  private Map<Integer, String> map = new HashMap<Integer, String>();

  public static void main(String[] argsthrows InterruptedException {
    for (int j = 1; j < 200; j++) {
      DemoThreadUnsafe demoClass = new DemoThreadUnsafe();
      for (int i = 0; i < 5; i++) {
        new Thread(new Unsafe_Hashmap_NoSynchronized_NotUsingContainsKey(demoClass.map)).start();
      }
      Thread.currentThread().sleep(10);
      System.out.println(demoClass.map.keySet());
      demoClass.map.clear();
    }
  }
}

1. Unsafe cont.

package unsafe.threads;

import java.util.Map;

/**
 * put operation is not safe on the map because its impl in 
 * hashmap (used by calling thread) is not synchronized
 * based on: http://lovehasija.com/2012/08/16/who-said-java-hashtable-is-thread-safe/
 
 @author sshakil
 *
 */
public class Unsafe_Hashmap_NoSynchronized_NotUsingContainsKey implements Runnable {
  private Map<Integer, String> map = null;

  public Unsafe_Hashmap_NoSynchronized_NotUsingContainsKey(Map<Integer, String> map) {
    this.map = map;
  }

  @Override
  public void run() {
    for (int i = 1; i <= 30; i++) {
      try {
        map.put(i, Thread.currentThread().getName());
      catch (Exception e) {
        System.out.println(e);
      }

    }
  }
}



2. Unsafe
package unsafe.demo;

import java.util.Hashtable;
import java.util.Map;

import unsafe.threads.Unsafe_Hashtable_NoSynchronized_UsingContainsKey;

public class DemoThreadUnsafe2 {
  private static volatile Map<Integer, String> map = new Hashtable<Integer, String>();

  public static void main(String[] argsthrows InterruptedException {
    for (int j = 1; j < 200; j++) {
      DemoThreadUnsafe2 demoClass = new DemoThreadUnsafe2();
      for (int i = 0; i < 10; i++) {
        new Thread(new Unsafe_Hashtable_NoSynchronized_UsingContainsKey(demoClass.map)).start();
      }
      Thread.currentThread().sleep(100);
      demoClass.map.clear();
    }
  }
}


2. Unsafe cont.

package unsafe.threads;

import java.util.Map;

/**
 * the sequence of map.containsKey(i) followed by the map.put sequence is not
 * safe because map's impl in hashmap (used by calling thread) is not
 * synchronized based on:
 * http://lovehasija.com/2012/08/16/who-said-java-hashtable-is-thread-safe/
 
 @author sshakil
 
 */
public class Unsafe_Hashtable_NoSynchronized_UsingContainsKey implements Runnable {

  private static volatile Map<Integer, String> map = null;

  public Unsafe_Hashtable_NoSynchronized_UsingContainsKey(Map<Integer, String> map) {
    this.map = map;
  }

  @Override
    public void run() {

         for (int i = 1; i <= 30; i++) {
          synchronized(map) {
             if(!map.containsKey(i)) {
                 //some other thread can modify data between the execution of 
                 //above and below lines
                 //try to catch it:
                 if(map.containsKey(i)) {
                   try {
              throw new Exception ("Detected mofification by another thread.");
            catch (Exception e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
            }
                 }
                 //more modifications can occur any time in above block
                System.out.printlnThread.currentThread().getName() 
                    "\tInserting new, Key exists: " + map.containsKey(i));
                map.put(i, Thread.currentThread().getName());
             }
             }
        }
    }
}


3. Unsafe

package unsafe.demo;

import java.util.concurrent.ConcurrentHashMap;

import safe.threads.Safe_Hashtable_Synchronized_UsingContainsKey;
import unsafe.threads.Unsafe_ConcurrentHashMap_NoSynchronized_UsingContainsKey;


public class DemoThreadUnsafe3 {

  // private Map<Integer, String> map = new Hashtable<Integer, String>();
  private ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<Integer, String>();

  public static void main(String[] argsthrows InterruptedException {

    for (int j = 1; j < 2000; j++) {
      DemoThreadUnsafe3 demoClass = new DemoThreadUnsafe3();

      for (int i = 0; i < 15; i++) {
        new Thread(
            new Unsafe_ConcurrentHashMap_NoSynchronized_UsingContainsKey(demoClass.map))
              .start();
      }

      Thread.currentThread().sleep(1);
      demoClass.map.clear();
    }
  }

}

3. Unsafe cont.

package unsafe.threads;
import java.util.Map;


public class Unsafe_ConcurrentHashMap_NoSynchronized_UsingContainsKey implements Runnable {
 
    private Map<Integer, String> map = null;
 
    public Unsafe_ConcurrentHashMap_NoSynchronized_UsingContainsKey(Map<Integer, String> map) {
         this.map = map;
    }
 
    @Override
    public void run() {

         for (int i = 1; i <= 30; i++) {
           //synchronized(map) {
               if(!map.containsKey(i)) {
                   if(map.containsKey(i)){
                     System.out.println("TRUE?");
                     try {
                throw new Exception("this can't be!");
              catch (Exception e) {
                e.printStackTrace();
              }
                   }
                  System.out.printlnThread.currentThread().getName() 
                      "\tInserting new, Key exists: " + map.containsKey(i));
                  map.put(i, Thread.currentThread().getName());
              }
           //}
        }
    }
}


Examples of safe usage:

1. Safe

package safe.demo;

import java.util.Hashtable;

import safe.threads.Safe_Hashtable_NoSynchronize_UsingPut;

/**
 * Thread safe hashmap usage through the use of 'synchronized' keyword. Based
 * on: http://lovehasija.com/2012/08/16/who-said-java-hashtable-is-thread-safe/
 
 @author sshakil
 
 */
public class PartiallySafe_BecauseOfHashtable {

  // private Map<Integer, String> map = new Hashtable<Integer, String>();
  private Hashtable<Integer, String> map = new Hashtable<Integer, String>();

  public static void main(String[] argsthrows InterruptedException {

    for (int j = 1; j < 100; j++) {
      PartiallySafe_BecauseOfHashtable demoClass = new PartiallySafe_BecauseOfHashtable();

      for (int i = 0; i < 5; i++) {
        new Thread(new Safe_Hashtable_NoSynchronize_UsingPut(demoClass.map)).start();
      }

      Thread.currentThread().sleep(10);
      System.out.println(demoClass.map.keySet());
      demoClass.map.clear();
    }
  }
}


1. Safe cont.

package safe.threads;

import java.util.Map;

/**
 * put operation is not safe on the map because its impl in 
 * hashmap (used by calling thread) is not synchronized
 * based on: http://lovehasija.com/2012/08/16/who-said-java-hashtable-is-thread-safe/
 
 @author sshakil
 *
 */
public class Safe_Hashtable_NoSynchronize_UsingPut implements Runnable {
  private Map<Integer, String> map = null;

  public Safe_Hashtable_NoSynchronize_UsingPut(Map<Integer, String> map) {
    this.map = map;
  }

  @Override
  public void run() {
    for (int i = 1; i <= 30; i++) {
      try {
        map.put(i, Thread.currentThread().getName());
      catch (Exception e) {
        System.out.println(e);
      }

    }
  }
}

2. Safe


package safe.demo;

import java.util.concurrent.ConcurrentHashMap;

import safe.threads.Safe_ConcurrentHashMap_NoSynchronize_UsingPutIfAbsent;

public class Safe_ConcurrentHashMap {

  private ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<Integer, String>(
      800.5f200);

  // private Map<Integer, String> map = new HashMap<Integer, String>();

  public static void main(String[] argsthrows InterruptedException {

    for (int j = 1; j < 100; j++) {
      Safe_ConcurrentHashMap demoClass = new Safe_ConcurrentHashMap();
      for (int i = 0; i < 5; i++) {
        new Thread(
              new Safe_ConcurrentHashMap_NoSynchronize_UsingPutIfAbsent(demoClass.map))
                .start();
      }

      Thread.currentThread().sleep(10);
      System.out.println(demoClass.map.keySet());
      demoClass.map.clear();
    }
  }
}


2. Safe cont.


package safe.threads;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Thread safe hashmap usage through the use of 'synchronized' keyword based on:
 * http://lovehasija.com/2012/08/16/who-said-java-hashtable-is-thread-safe/
 
 @author sshakil
 */
public class Safe_ConcurrentHashMap_NoSynchronize_UsingPutIfAbsent implements Runnable {

  //private Map<Integer, String> map = null;
  private ConcurrentHashMap<Integer, String> map = null;

  public Safe_ConcurrentHashMap_NoSynchronize_UsingPutIfAbsent(
      ConcurrentHashMap<Integer, String> map) {
  //public ThreadSafeMapUsage3(Map<Integer, String> map) {
    this.map = map;
  }

  @Override
  public void run() {
    for (int i = 1; i <= 30; i++) {
      //String result = map.put(i, Thread.currentThread().getName());
      String result = map.putIfAbsent(i, Thread.currentThread().getName());    
    }
  }
}


3. Safe

package safe.demo;

import java.util.concurrent.ConcurrentHashMap;

import safe.threads.Safe_ConcurrentHashMap_NoSynchronize_UsingPutIfAbsent;

public class Safe_ConcurrentHashMap {

  private ConcurrentHashMap<Integer, String> map = new ConcurrentHashMap<Integer, String>(
      800.5f200);

  // private Map<Integer, String> map = new HashMap<Integer, String>();

  public static void main(String[] argsthrows InterruptedException {

    for (int j = 1; j < 100; j++) {
      Safe_ConcurrentHashMap demoClass = new Safe_ConcurrentHashMap();
      for (int i = 0; i < 5; i++) {
        new Thread(
              new Safe_ConcurrentHashMap_NoSynchronize_UsingPutIfAbsent(demoClass.map))
                .start();
      }

      Thread.currentThread().sleep(10);
      System.out.println(demoClass.map.keySet());
      demoClass.map.clear();
    }
  }

}


3. Safe cont.

package safe.threads;

import java.util.Map;

/**
 * Thread safe hashmap usage through the use of 'synchronized' keyword based on:
 * http://lovehasija.com/2012/08/16/who-said-java-hashtable-is-thread-safe/
 
 @author sshakil
 */
public class Safe_Hashtable_Synchronized_UsingContainsKey implements Runnable {

  private Map<Integer, String> map = null;

  public Safe_Hashtable_Synchronized_UsingContainsKey(Map<Integer, String> map) {
    this.map = map;
  }

  @Override
  public void run() {

    for (int i = 1; i <= 30; i++) {
      synchronized (map) {
        if (!map.containsKey(i)) {
          if (map.containsKey(i)) {
            System.out.println("TRUE?");
            try {
              throw new Exception("this can't be!");
            catch (Exception e) {
              e.printStackTrace();
            }
          }
          System.out.println(Thread.currentThread().getName()
              "\tInserting new, Key exists: "
              + map.containsKey(i));
          map.put(i, Thread.currentThread().getName());
        }
      }
    }
  }
}


 


No comments:

Post a Comment