Showing posts with label 5.0 Feature. Show all posts
Showing posts with label 5.0 Feature. Show all posts

Saturday, November 6, 2010

The Lock API

The Java 5.0 comes with new features in the concurrency package to take care of the locking strategy. The previous locking in Java 2.0 had a lot of disadvantages, like once a thread is waiting for a lock its not possible to interrupt it. Also the thread will wait for ever if its not able to acquire the lock. The order of acquiring the lock is also dependent on the thread scheduler and there is no way we can force the scheduler to give a prticular thread more priority in acquiring a lock. Intrinsic lock also needs to be released in the same block in which it acquires the lock.

All the above problems has been answered in the updates in Java 5.0 with the advent of the Lock package. We can acquire a lock equivalent to a synchronized block by using the lock() method in the ReentrantLock class. The example of Blocking Queue with wait() notify() can be rewritten with the lock strategy.

public class BlockingQueue5 extends BlockingQueue {
 
   private Queue queue = new LinkedList();
     String testingwait = new String();
     Lock lock = new ReentrantLock();
     Condition notFull = lock.newCondition();
     Condition notEmpty = lock.newCondition();
     private int capacity;

     public BlockingQueue5(int capacity) {
      super(capacity);
         this.capacity = capacity;
     }

     public void put(T element) throws InterruptedException {
      lock.lock();
         try {
    while(queue.size() == capacity) {
     
     notFull.await();
        
    }
    
    queue.add(element);
    notEmpty.signal();
   } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }finally{
    lock.unlock();
   }
     }

     public T take() throws InterruptedException {
      lock.lock();
         try {
    while(queue.isEmpty()) {
     
        notEmpty.await();
        
    }
    
    T item = queue.remove();
    notFull.signal();
    return item;
   } catch (InterruptedException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
   }finally{
    lock.unlock();
   }
   return null;
     }

}

As can be seen the synchronized has been replaced by the lock(). Before any task is started the lock should be acquired . Its also imperative that the lock should be released come what may, so its essential that the release section should be in a  finally block. The wait and notify has been replaced by the Condition object. The await () and the signal()  methods  are equivalent to wait and notify. It necessary that the condition on which the await has been called , the signal is also called on the same condition.

Another method in the Lock class is the tryLock(). It solves the problems of deadlocking as seen in Dead Lock situation. The tryLock also comes with a overloaded method which takes in nanoseconds as parameters       which means the number of nano secs it will wait to acquire a lock after which it can resume with other operations. The tryLock acquires the lock if no other thread is waiting on that lock and in a thread ordering situation it gets the first preference. The  TransactionTryLock class below replaces the Transaction class and shows how tryLock can avoid deadlock situation.
public class TransactionTryLock extends Transaction{
 
 
private Lock accountA = new ReentrantLock();
private Lock accountB = new ReentrantLock();

public void transferA2B(){
 if(accountA.tryLock()) {
  System.out.println("lock for A held in A2B");
  try {
   // sleep given to force deadlock
   Thread.sleep(1000);
  } catch (InterruptedException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  
  if(accountB.tryLock()) {
   System.out.println("lock for B held in A2B");
   
  }

 }
}

public void transferB2A(){
 if(accountB.tryLock()) {
  System.out.println("lock for B held in B2A");
  try {
   // sleep given to force deadlock
   Thread.sleep(1000);
  } catch (InterruptedException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
  if(accountA.tryLock()) {
   
   System.out.println("lock for A held in B2A");
   
  }

 }
}

}

Sunday, October 31, 2010

Atomicity

What happens when we add one element of state to what was a stateless object? Suppose we want to add a "hit counter" that measures the number of requests processed. The obvious approach is to add a long field to the servlet and increment it on each request.

public class UnsafeCountingFactorizer implements Servlet {

private long count = 0;

public long getCount() {

return count;

}

public void service(ServletRequest req, ServletResponse resp) {

BigInteger i = extractFromRequest(req);

BigInteger[] factors = factor(i);

++count;

encodeIntoResponse(resp, factors);

}

}

Unfortunately, UnsafeCountingFactorizer is not thread-safe, even though  it would work just fine in a single-threaded environment. It is susceptible to lost updates. While the increment operation,  ++count, may look like a single action because of its compact syntax,  it is not atomic. shows what can happen if two threads try to increment a counter simultaneously  without synchronization. If the counter is initially 9, with some unlucky timing  each thread could read the value, see that it is 9, add one to it, and each set  the counter to 10. This is clearly not what is supposed to happen; an increment  got lost along the way, and the hit counter is now permanently off by one. This is known is Race Condition.

UnsafeCountingFactorizer has several race conditions that make its results unreliable. A race condition occurs when the correctness of a computation depends on the relative timing or interleaving of multiple threads by the runtime; in other words, when getting the right answer relies on lucky timing.

Race Condition can be avoided by lazy initialization

public class LazyInitRace {

private ExpensiveObject instance = null;

public ExpensiveObject getInstance() {

if (instance ==null){

instance = new ExpensiveObject();

}

return instance;

}

}

LazyInitRace has race conditions that can undermine its correctness. Say that threads A and B execute getInstance at the same time. A sees that instance is null, and instantiates a new ExpensiveObject. B also checks if instance is null. Whether instance is null at this point depends unpredictably on timing, including the vagaries of scheduling and how long A takes to instantiate the ExpensiveObject and set the instance field. If instance is null when B examines it, the two callers to getInstance may receive two different results, even though getInstance is always supposed to return the same instance.

If the increment operation in UnsafeSequence were atomic, the race conditioncould not occur, and each execution of the increment operation would have the desired effect of incrementing the counter by exactly one. To ensure thread safety, check-then-act operations (like lazy initialization) and read-modify-write operations (like increment) must always be atomic. Atomic state can be created by the syncronization or the free atomic package in new Java 1.5

public class CountingFactorizer implements Servlet { 
    private final AtomicLong count = new AtomicLong(0); 
     public long getCount() {
       return count.get(); 
     } 
 public void service(ServletRequest req, ServletResponse resp) {                                
         BigInteger i = extractFromRequest(req);    
         BigInteger[] factors = factor(i);
         count.incrementAndGet();
         encodeIntoResponse(resp, factors);
     }
 } 


The java.util.concurrent.atomic package contains atomic variable classes for effecting atomic state transitions on numbers and object references. By replacing the long counter with an AtomicLong, we ensure that all actions that access the counter state are atomic. Because the state of the servlet is the state of the counter and the counter is thread-safe, our servlet is once again thread-safe.We were able to add a counter to our factoring servlet and maintain thread safety by using an existing thread-safe class to manage the counter state, AtomicLong. When a single element of state is added to a stateless class, the resulting class will be thread-safe if the state is entirely managed by a thread-safe object. But, as we'll see in the next section, going from one state variable to more than one is not necessarily as simple as going from zero to one.

Where practical, use existing thread-safe objects, like AtomicLong, to manage your class's state. It is simpler to reason about the possible states and state transitions for existing thread-safe objects than it is for arbitrary state variables, and this makes it easier to maintain and verify thread safety.