Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Java 101: The next generation: Java concurrency without the pain, Part 2

Locking, atomic variables, Fork/Join, and what to expect in Java 8

  • Print
  • Feedback

Page 6 of 7

The ReadWriteLock interface maintains a pair of associated locks, one for read-only operations and one for write operations. The read lock may be held simultaneously by multiple reader threads as long as there are no writers. The write lock is exclusive: only a single thread can modify shared data. (The lock that's associated with the synchronized keyword is also exclusive.)

ReadWriteLock declares the following methods:

  • Lock readLock() returns the lock that's used for reading.
  • Lock writeLock() returns the lock that's used for writing.

Working with read-write locks

The ReentrantReadWriteLock class implements ReadWriteLock and describes a read-write lock with similar semantics to a reentrant lock. Like ReentrantLock, ReentrantReadWriteLock declares a pair of constructors:

  • ReentrantReadWriteLock() creates a reentrant read-write lock with default (nonfair) ordering properties.
  • ReentrantReadWriteLock(boolean fair) creates a reentrant read-write lock with the given fairness policy.

ReentrantReadWriteLock implements ReadWriteLock's methods and provides additional methods, including the following trio:

  • int getQueueLength() returns an estimate of the number of threads waiting to acquire either the read or write lock.
  • int getReadHoldCount() returns the number of read holds on this lock by the current thread. A reader thread has a hold on a lock for each lock action that is not matched by an unlock action.
  • boolean hasWaiters(Condition condition) returns true when there are threads waiting on the given condition associated with the write lock.

Listing 3 demonstrates ReentrantReadWriteLock.

LIsting 3. RWLockDemo.java

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class RWLockDemo
{
   final static int DELAY = 80;
   final static int NUMITER = 5;

   public static void main(String[] args) 
   {
      final Names names = new Names();

      class NamedThread implements ThreadFactory
      {
         private String name;

         NamedThread(String name)
         {
            this.name = name;
         }

         @Override
         public Thread newThread(Runnable r)
         {
            return new Thread(r, name);
         }
      }

      ExecutorService writer;
      writer = Executors.newSingleThreadExecutor(new NamedThread("writer"));
      Runnable wrunnable = new Runnable()
                           {
                              @Override
                              public void run()
                              {
                                 for (int i = 0; i < NUMITER; i++)
                                 {
                                    names.add(Thread.currentThread().getName(), 
                                              "A" + i);
                                    try
                                    {
                                       Thread.sleep(DELAY);
                                    }
                                    catch (InterruptedException ie)
                                    {
                                    }
                                 }
                              }
                           };
      writer.submit(wrunnable);

      ExecutorService reader1;
      reader1 = Executors.newSingleThreadExecutor(new NamedThread("reader1"));
      ExecutorService reader2;
      reader2 = Executors.newSingleThreadExecutor(new NamedThread("reader2"));
      Runnable rrunnable = new Runnable()
                           {
                              @Override
                              public void run()
                              {
                                 for (int i = 0; i < NUMITER; i++)
                                    names.dump(Thread.currentThread().getName());
                              }
                           };
      reader1.submit(rrunnable);
      reader2.submit(rrunnable);

      reader1.shutdown();
      reader2.shutdown();
      writer.shutdown();
   }
}

class Names 
{
   private final List<String> names;
 
   private final ReentrantReadWriteLock lock;
   private final Lock readLock, writeLock;
 
   Names()
   {
      names = new ArrayList<>();
      lock = new ReentrantReadWriteLock();
      readLock = lock.readLock();
      writeLock = lock.writeLock();
   }
 
   void add(String threadName, String name)
   {
      writeLock.lock();
      try 
      {
         System.out.printf("%s: num waiting threads = %d%n", 
                           threadName, lock.getQueueLength());
         names.add(name);
      } 
      finally 
      {
         writeLock.unlock();
      }
   }

   void dump(String threadName)
   {
      readLock.lock();
      try
      {
         System.out.printf("%s: num waiting threads = %d%n",
                           threadName, lock.getQueueLength());
         Iterator<String> iter = names.iterator();
         while (iter.hasNext())
         {
            System.out.printf("%s: %s%n", threadName, iter.next());
            try
            {
               Thread.sleep((int)(Math.random()*100));
            }
            catch (InterruptedException ie)
            {
            }
         }
      }
      finally
      {
         readLock.unlock();
      }
   }
}

Listing 3 describes an application where a writer thread appends names to a list of names and a pair of reader threads repeatedly dump this list to the standard output.

  • Print
  • Feedback

Resources

Previous articles in Java 101: The next generation

Concurrency tutorials on JavaWorld:

  • Modern threading for not-quite-beginners (Cameron Laird, JavaWorld, January 2013): Get an overview of callable and runnable, learn more about synchronized blocks, and find out how you can use java.util.concurrent to work around deadlock and similar threading pitfalls.
  • Multicore processing for client-side Java applications (Kirill Grouchnikov, JavaWorld, September 2007): Get a hands-on introduction to collection sorting using the CountDownLatch and Executors.newFixedThreadPool concurrency utilities.
  • Java concurrency with thread gates (Obi Ezechukwu, JavaWorld, March 2009): See the Java concurrency utilities at work in a realistic implementation of the Thread Gates concurrency pattern.
  • Hyper-threaded Java (Randall Scarberry, JavaWorld, November 2006): See for yourself how two java.util.concurrent classes were used to optimize thread use for faster performance in a real-world application.
  • Java Tip 144: When to use ForkJoinPool vs ExecutorService (Madalin Ilie, JavaWorld, October 2011): Demonstrates the performance impact of replacing the standard ExecutorService class with ForkJoinPool in a web crawler.