Save This Page
Home » tapestry-src-5.0.19 » org.apache.tapestry5.ioc.internal.util » [javadoc | source]
    1   // Copyright 2006, 2007 The Apache Software Foundation
    2   //
    3   // Licensed under the Apache License, Version 2.0 (the "License");
    4   // you may not use this file except in compliance with the License.
    5   // You may obtain a copy of the License at
    6   //
    7   //     http://www.apache.org/licenses/LICENSE-2.0
    8   //
    9   // Unless required by applicable law or agreed to in writing, software
   10   // distributed under the License is distributed on an "AS IS" BASIS,
   11   // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   12   // See the License for the specific language governing permissions and
   13   // limitations under the License.
   14   
   15   package org.apache.tapestry5.ioc.internal.util;
   16   
   17   import java.util.concurrent.TimeUnit;
   18   import java.util.concurrent.locks.ReadWriteLock;
   19   import java.util.concurrent.locks.ReentrantReadWriteLock;
   20   
   21   /**
   22    * A barrier used to execute code in a context where it is guarded by read/write locks. In addition, handles upgrading
   23    * read locks to write locks (and vice versa). Execution of code within a lock is in terms of a {@link Runnable} object
   24    * (that returns no value), or a {@link Invokable} object (which does return a value).
   25    */
   26   public class ConcurrentBarrier
   27   {
   28       private final ReadWriteLock lock = new ReentrantReadWriteLock();
   29   
   30       /**
   31        * This is, of course, a bit of a problem. We don't have an avenue for ensuring that this ThreadLocal is destroyed
   32        * at the end of the request, and that means a thread can hold a reference to the class and the class loader which
   33        * loaded it. This may cause redeployment problems (leaked classes and class loaders). Apparently JDK 1.6 provides
   34        * the APIs to check to see if the current thread has a read lock. So, we tend to remove the TL, rather than set its
   35        * value to false.
   36        */
   37       private static class ThreadBoolean extends ThreadLocal<Boolean>
   38       {
   39           @Override
   40           protected Boolean initialValue()
   41           {
   42               return false;
   43           }
   44       }
   45   
   46       private final ThreadBoolean threadHasReadLock = new ThreadBoolean();
   47   
   48       /**
   49        * Invokes the object after acquiring the read lock (if necessary). If invoked when the read lock has not yet been
   50        * acquired, then the lock is acquired for the duration of the call. If the lock has already been acquired, then the
   51        * status of the lock is not changed.
   52        * <p/>
   53        * TODO: Check to see if the write lock is acquired and <em>not</em> acquire the read lock in that situation.
   54        * Currently this code is not re-entrant. If a write lock is already acquired and the thread attempts to get the
   55        * read lock, then the thread will hang. For the moment, all the uses of ConcurrentBarrier are coded in such a way
   56        * that reentrant locks are not a problem.
   57        *
   58        * @param <T>
   59        * @param invokable
   60        * @return the result of invoking the invokable
   61        */
   62       public <T> T withRead(Invokable<T> invokable)
   63       {
   64           boolean readLockedAtEntry;
   65   
   66           synchronized (threadHasReadLock)
   67           {
   68               readLockedAtEntry = threadHasReadLock.get();
   69           }
   70   
   71           if (!readLockedAtEntry)
   72           {
   73               lock.readLock().lock();
   74   
   75               synchronized (threadHasReadLock)
   76               {
   77                   threadHasReadLock.set(true);
   78               }
   79           }
   80   
   81           try
   82           {
   83               return invokable.invoke();
   84           }
   85           finally
   86           {
   87               if (!readLockedAtEntry)
   88               {
   89                   lock.readLock().unlock();
   90   
   91                   synchronized (threadHasReadLock)
   92                   {
   93                       threadHasReadLock.remove();
   94                   }
   95               }
   96           }
   97       }
   98   
   99       /**
  100        * As with {@link #withRead(Invokable)}, creating an {@link Invokable} wrapper around the runnable object.
  101        */
  102       public void withRead(final Runnable runnable)
  103       {
  104           Invokable<Void> invokable = new Invokable<Void>()
  105           {
  106               public Void invoke()
  107               {
  108                   runnable.run();
  109   
  110                   return null;
  111               }
  112           };
  113   
  114           withRead(invokable);
  115       }
  116   
  117       /**
  118        * Acquires the exclusive write lock before invoking the Invokable. The code will be executed exclusively, no other
  119        * reader or writer threads will exist (they will be blocked waiting for the lock). If the current thread has a read
  120        * lock, it is released before attempting to acquire the write lock, and re-acquired after the write lock is
  121        * released. Note that in that short window, between releasing the read lock and acquiring the write lock, it is
  122        * entirely possible that some other thread will sneak in and do some work, so the {@link Invokable} object should
  123        * be prepared for cases where the state has changed slightly, despite holding the read lock. This usually manifests
  124        * as race conditions where either a) some parallel unrelated bit of work has occured or b) duplicate work has
  125        * occured. The latter is only problematic if the operation is very expensive.
  126        *
  127        * @param <T>
  128        * @param invokable
  129        */
  130       public <T> T withWrite(Invokable<T> invokable)
  131       {
  132           boolean readLockedAtEntry = releaseReadLock();
  133   
  134           lock.writeLock().lock();
  135   
  136           try
  137           {
  138               return invokable.invoke();
  139           }
  140           finally
  141           {
  142               lock.writeLock().unlock();
  143               restoreReadLock(readLockedAtEntry);
  144           }
  145       }
  146   
  147       private boolean releaseReadLock()
  148       {
  149           boolean readLockedAtEntry;
  150   
  151           synchronized (threadHasReadLock)
  152           {
  153               readLockedAtEntry = threadHasReadLock.get();
  154           }
  155   
  156           if (readLockedAtEntry)
  157           {
  158               lock.readLock().unlock();
  159   
  160               synchronized (threadHasReadLock)
  161               {
  162                   threadHasReadLock.set(false);
  163               }
  164           }
  165   
  166           return readLockedAtEntry;
  167       }
  168   
  169       private void restoreReadLock(boolean readLockedAtEntry)
  170       {
  171           if (readLockedAtEntry)
  172           {
  173               lock.readLock().lock();
  174   
  175               synchronized (threadHasReadLock)
  176               {
  177                   threadHasReadLock.set(true);
  178               }
  179           }
  180           else
  181           {
  182               synchronized (threadHasReadLock)
  183               {
  184                   threadHasReadLock.remove();
  185               }
  186           }
  187       }
  188   
  189       /**
  190        * As with {@link #withWrite(Invokable)}, creating an {@link Invokable} wrapper around the runnable object.
  191        */
  192       public void withWrite(final Runnable runnable)
  193       {
  194           Invokable<Void> invokable = new Invokable<Void>()
  195           {
  196               public Void invoke()
  197               {
  198                   runnable.run();
  199   
  200                   return null;
  201               }
  202           };
  203   
  204           withWrite(invokable);
  205       }
  206   
  207       /**
  208        * Try to aquire the exclusive write lock and invoke the Runnable. If the write lock is obtained within the specfied
  209        * timeout, then this method behaves as {@link #withWrite(Runnable)} and will return true. If the write lock is not
  210        * obtained within the timeout then the runnable is never invoked and the method will return false.
  211        *
  212        * @param runnable    Runnable object to execute inside the write lock.
  213        * @param timeout     Time to wait for write lock.
  214        * @param timeoutUnit Units of timeout.
  215        * @return true if lock was obtained & runnabled executed. False otherwise.
  216        */
  217       public boolean tryWithWrite(final Runnable runnable, long timeout, TimeUnit timeoutUnit)
  218       {
  219           boolean readLockedAtEntry = releaseReadLock();
  220   
  221           boolean obtainedLock = false;
  222   
  223           try
  224           {
  225               try
  226               {
  227                   obtainedLock = lock.writeLock().tryLock(timeout, timeoutUnit);
  228   
  229                   if (obtainedLock) runnable.run();
  230   
  231               }
  232               catch (InterruptedException e)
  233               {
  234                   obtainedLock = false;
  235               }
  236               finally
  237               {
  238                   if (obtainedLock) lock.writeLock().unlock();
  239               }
  240           }
  241           finally
  242           {
  243               restoreReadLock(readLockedAtEntry);
  244           }
  245   
  246           return obtainedLock;
  247       }
  248   
  249   }

Save This Page
Home » tapestry-src-5.0.19 » org.apache.tapestry5.ioc.internal.util » [javadoc | source]