View Javadoc

1   /**
2    * Copyright 2008 Atlassian Pty Ltd 
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License"); 
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at 
7    * 
8    *     http://www.apache.org/licenses/LICENSE-2.0 
9    * 
10   * Unless required by applicable law or agreed to in writing, software 
11   * distributed under the License is distributed on an "AS IS" BASIS, 
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
13   * See the License for the specific language governing permissions and 
14   * limitations under the License.
15   */
16  
17  package com.atlassian.util.concurrent;
18  
19  import java.lang.ref.Reference;
20  import java.lang.ref.WeakReference;
21  import java.util.concurrent.Callable;
22  import java.util.concurrent.ExecutionException;
23  import java.util.concurrent.FutureTask;
24  
25  import net.jcip.annotations.ThreadSafe;
26  
27  /**
28   * Lazily loaded reference that is not constructed until required. This class is
29   * used to maintain a reference to an object that is expensive to create and
30   * must be constructed once and once only. This reference behaves as though the
31   * <code>final</code> keyword has been used (you cannot reset it once it has
32   * been constructed).
33   * <p>
34   * Usage: clients need to implement the {@link #create()} method to return the
35   * object this reference will hold.
36   * <p>
37   * For instance:
38   * <p>
39   * 
40   * <pre>
41   * final LazyReference&lt;MyObject&gt; ref = new LazyReference() {
42   *     protected MyObject create() throws Exception {
43   *         // Do expensive object construction here
44   *         return new MyObject();
45   *     }
46   * };
47   * </pre>
48   * 
49   * Then call {@link #get()} to get a reference to the referenced object:
50   * 
51   * <pre>
52   * MyObject myLazyLoadedObject = ref.get()
53   * </pre>
54   * 
55   * NOTE: Interruption policy is that if you want to be cancellable while waiting
56   * for another thread to create the value, instead of calling {@link #get()}
57   * call {@link #getInterruptibly()}. However, If your {@link #create()} method
58   * is interrupted and throws an {@link InterruptedException}, it is treated as
59   * an application exception and will be the causal exception inside the runtime
60   * {@link InitializationException} that {@link #get()} or
61   * {@link #getInterruptibly()} throws and your {@link #create()} will not be
62   * called again.
63   * <p>
64   * Implementation note. This class extends {@link WeakReference} as
65   * {@link Reference} does not have a public constructor. WeakReference is
66   * preferable as it does not have any members and therefore doesn't increase the
67   * memory footprint. As we never pass a referent through to the super-class and
68   * override {@link #get()}, the garbage collection semantics of WeakReference
69   * are irrelevant. The referenced object will not become eligible for GC unless
70   * the object holding the reference to this object is collectible.
71   */
72  @ThreadSafe
73  public abstract class LazyReference<T> extends WeakReference<T> {
74      private final FutureTask<T> future = new FutureTask<T>(new Callable<T>() {
75          public T call() throws Exception {
76              return create();
77          }
78      });
79  
80      public LazyReference() {
81          super(null);
82      }
83  
84      /**
85       * The object factory method, guaranteed to be called once and only once.
86       * 
87       * @return the object that {@link #get()} and {@link #getInterruptibly()}
88       * will return.
89       * @throws Exception if anything goes wrong, rethrown as an
90       * InitializationException from {@link #get()} and
91       * {@link #getInterruptibly()}
92       */
93      protected abstract T create() throws Exception;
94  
95      /**
96       * Get the lazily loaded reference in a non-cancellable manner. If your
97       * <code>create()</code> method throws an Exception calls to
98       * <code>get()</code> will throw an InitializationException which wraps the
99       * previously thrown exception.
100      * 
101      * @return the object that {@link #create()} created.
102      * @throws InitializationException if the {@link #create()} method throws an
103      * exception. The {@link InitializationException#getCause()} will contain
104      * the exception thrown by the {@link #create()} method
105      */
106     @Override
107     public final T get() {
108         boolean interrupted = false;
109         try {
110             while (true) {
111                 try {
112                     return getInterruptibly();
113                 } catch (final InterruptedException ignore) {
114                     // ignore and try again
115                     interrupted = true;
116                 }
117             }
118         } finally {
119             if (interrupted) {
120                 Thread.currentThread().interrupt();
121             }
122         }
123     }
124 
125     /**
126      * Get the lazily loaded reference in a cancellable manner. If your
127      * <code>create()</code> method throws an Exception, calls to
128      * <code>get()</code> will throw a RuntimeException which wraps the
129      * previously thrown exception.
130      * 
131      * @return the object that {@link #create()} created.
132      * @throws InitializationException if the {@link #create()} method throws an
133      * exception. The {@link InitializationException#getCause()} will contain
134      * the exception thrown by the {@link #create()} method
135      * @throws InterruptedException If the calling thread is Interrupted while
136      * waiting for another thread to create the value (if the creating thread is
137      * interrupted while blocking on something, the {@link InterruptedException}
138      * will be thrown as the causal exception of the
139      * {@link InitializationException} to everybody calling this method).
140      */
141     public final T getInterruptibly() throws InterruptedException {
142         if (!future.isDone()) {
143             future.run();
144         }
145 
146         try {
147             return future.get();
148         } catch (final ExecutionException e) {
149             throw new InitializationException(e);
150         }
151     }
152 
153     /**
154      * Has the {@link #create()} reference been initialized.
155      * 
156      * @return true if the task is complete
157      */
158     public boolean isInitialized() {
159         return future.isDone();
160     }
161 
162     /**
163      * Cancel the initializing operation if it has not already run. Will try and
164      * interrupt if it is currently running.
165      */
166     public void cancel() {
167         future.cancel(true);
168     }
169 
170     /**
171      * If the factory {@link LazyReference#create()} method threw an exception,
172      * this wraps it.
173      */
174     public static class InitializationException extends RuntimeException {
175         private static final long serialVersionUID = 3638376010285456759L;
176 
177         InitializationException(final ExecutionException e) {
178             super((e.getCause() != null) ? e.getCause() : e);
179         }
180     }
181 }