View Javadoc

1   /*
2    * Copyright 2002-2008 the original author or authors.
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 org.springframework.util;
18  
19  import java.io.IOException;
20  import java.io.ObjectInputStream;
21  import java.io.Serializable;
22  
23  import org.apache.commons.logging.Log;
24  import org.apache.commons.logging.LogFactory;
25  
26  /**
27   * Support class for throttling concurrent access to a specific resource.
28   *
29   * <p>Designed for use as a base class, with the subclass invoking
30   * the {@link #beforeAccess()} and {@link #afterAccess()} methods at
31   * appropriate points of its workflow. Note that <code>afterAccess</code>
32   * should usually be called in a finally block!
33   *
34   * <p>The default concurrency limit of this support class is -1
35   * ("unbounded concurrency"). Subclasses may override this default;
36   * check the javadoc of the concrete class that you're using.
37   *
38   * @author Juergen Hoeller
39   * @since 1.2.5
40   * @see #setConcurrencyLimit
41   * @see #beforeAccess()
42   * @see #afterAccess()
43   * @see org.springframework.aop.interceptor.ConcurrencyThrottleInterceptor
44   * @see java.io.Serializable
45   */
46  public abstract class ConcurrencyThrottleSupport implements Serializable {
47  
48  	/**
49  	 * Permit any number of concurrent invocations: that is, don't throttle concurrency.
50  	 */
51  	public static final int UNBOUNDED_CONCURRENCY = -1;
52  
53  	/**
54  	 * Switch concurrency 'off': that is, don't allow any concurrent invocations.
55  	 */
56  	public static final int NO_CONCURRENCY = 0;
57  	
58  
59  	/** Transient to optimize serialization */
60  	protected transient Log logger = LogFactory.getLog(getClass());
61  
62  	private transient Object monitor = new Object();
63  
64  	private int concurrencyLimit = UNBOUNDED_CONCURRENCY;
65  
66  	private int concurrencyCount = 0;
67  
68  
69  	/**
70  	 * Set the maximum number of concurrent access attempts allowed.
71  	 * -1 indicates unbounded concurrency.
72  	 * <p>In principle, this limit can be changed at runtime,
73  	 * although it is generally designed as a config time setting.
74  	 * <p>NOTE: Do not switch between -1 and any concrete limit at runtime,
75  	 * as this will lead to inconsistent concurrency counts: A limit
76  	 * of -1 effectively turns off concurrency counting completely.
77  	 */
78  	public void setConcurrencyLimit(int concurrencyLimit) {
79  		this.concurrencyLimit = concurrencyLimit;
80  	}
81  
82  	/**
83  	 * Return the maximum number of concurrent access attempts allowed.
84  	 */
85  	public int getConcurrencyLimit() {
86  		return this.concurrencyLimit;
87  	}
88  
89  	/**
90  	 * Return whether this throttle is currently active.
91  	 * @return <code>true</code> if the concurrency limit for this instance is active
92  	 * @see #getConcurrencyLimit()
93  	 */
94  	public boolean isThrottleActive() {
95  		return (this.concurrencyLimit > 0);
96  	}
97  
98  
99  	/**
100 	 * To be invoked before the main execution logic of concrete subclasses.
101 	 * <p>This implementation applies the concurrency throttle.
102 	 * @see #afterAccess() 
103 	 */
104 	protected void beforeAccess() {
105 		if (this.concurrencyLimit == NO_CONCURRENCY) {
106 			throw new IllegalStateException(
107 					"Currently no invocations allowed - concurrency limit set to NO_CONCURRENCY");
108 		}
109 		if (this.concurrencyLimit > 0) {
110 			boolean debug = logger.isDebugEnabled();
111 			synchronized (this.monitor) {
112 				boolean interrupted = false;
113 				while (this.concurrencyCount >= this.concurrencyLimit) {
114 					if (interrupted) {
115 						throw new IllegalStateException("Thread was interrupted while waiting for invocation access, " +
116 								"but concurrency limit still does not allow for entering");
117 					}
118 					if (debug) {
119 						logger.debug("Concurrency count " + this.concurrencyCount +
120 								" has reached limit " + this.concurrencyLimit + " - blocking");
121 					}
122 					try {
123 						this.monitor.wait();
124 					}
125 					catch (InterruptedException ex) {
126 						// Re-interrupt current thread, to allow other threads to react.
127 						Thread.currentThread().interrupt();
128 						interrupted = true;
129 					}
130 				}
131 				if (debug) {
132 					logger.debug("Entering throttle at concurrency count " + this.concurrencyCount);
133 				}
134 				this.concurrencyCount++;
135 			}
136 		}
137 	}
138 
139 	/**
140 	 * To be invoked after the main execution logic of concrete subclasses.
141 	 * @see #beforeAccess()
142 	 */
143 	protected void afterAccess() {
144 		if (this.concurrencyLimit >= 0) {
145 			synchronized (this.monitor) {
146 				this.concurrencyCount--;
147 				if (logger.isDebugEnabled()) {
148 					logger.debug("Returning from throttle at concurrency count " + this.concurrencyCount);
149 				}
150 				this.monitor.notify();
151 			}
152 		}
153 	}
154 
155 
156 	//---------------------------------------------------------------------
157 	// Serialization support
158 	//---------------------------------------------------------------------
159 
160 	private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
161 		// Rely on default serialization, just initialize state after deserialization.
162 		ois.defaultReadObject();
163 
164 		// Initialize transient fields.
165 		this.logger = LogFactory.getLog(getClass());
166 		this.monitor = new Object();
167 	}
168 
169 }