package com.kendelong.util.concurrency; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.lang3.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; import org.springframework.jmx.export.annotation.ManagedAttribute; import org.springframework.jmx.export.annotation.ManagedOperation; import org.springframework.jmx.export.annotation.ManagedResource; import com.kendelong.util.monitoring.graphite.GraphiteClient; /** * This is an interceptor that will prevent more than a certain number of threads accessing a given resource * at the same time. Usage could be * * <pre> * {@code <bean class="com.kendelong.util.concurrency.ConcurrencyLimitingAspect" scope="prototype"> <property name="graphiteClient" ref="graphiteClient"/> </bean> <bean class="com.kendelong.util.spring.JmxExportingAspectPostProcessor" lazy-init="false"> <property name="mbeanExporter" ref="mbeanExporter"/> <property name="annotationToServiceNames"> <map> <entry key="com.kendelong.util.concurrency.ConcurrencyLimitingAspect" value="concurrencyThrottle" /> </map> </property> <property name="jmxDomain" value="app.mystuff"/> </bean> } </pre> * * * @author kdelong */ @Aspect @ManagedResource(description="An interceptor that limits the number of threads that can be in a component at any one time") @Order(300) public class ConcurrencyLimitingAspect implements Ordered { private final AtomicInteger threadLimit = new AtomicInteger(20); private final AtomicInteger threadCount = new AtomicInteger(); private final AtomicInteger tripCount = new AtomicInteger(); private GraphiteClient graphiteClient; private int order = 0; // @Around("@annotation(com.kendelong.util.concurrency.ConcurrencyThrottle)") // public Object applyConcurrencyThrottle(ProceedingJoinPoint pjp) throws Throwable @Around("@annotation(ann)") public Object applyConcurrencyThrottle(ProceedingJoinPoint pjp, ConcurrencyThrottle ann) throws Throwable { this.threadLimit.set(ann.threadLimit()); String key = null; if(graphiteClient != null) { String classKey = StringUtils.substringAfterLast(pjp.getSignature().getDeclaringTypeName(), "."); String methodName = pjp.getSignature().getName(); key = "concurrencyThrottle." + classKey + "." + methodName; graphiteClient.increment(key + ".accesses"); } Object result; try { int threadNum = threadCount.incrementAndGet(); if(threadNum > getThreadLimit()) { tripCount.incrementAndGet(); if(graphiteClient != null) graphiteClient.increment(key + ".trips"); throw new ConcurrencyLimitExceededException("This thread exceeded the thread limit of " + getThreadLimit()); } result = pjp.proceed(); } finally { threadCount.decrementAndGet(); } return result; } @ManagedAttribute(description="The maximum number of threads allowed in the component at one time") public int getThreadLimit() { return threadLimit.get(); } @ManagedAttribute public void setThreadLimit(int val) { threadLimit.set(val); } @ManagedAttribute(description="The current number of threads in the component") public int getThreadCount() { return threadCount.get(); } @ManagedAttribute(description="The number of accesses that were rejected due to the maximum number of threads being reached") public int getTripCount() { return tripCount.get(); } @ManagedOperation(description="Reset the trip count to zero") public void resetStatistics() { tripCount.set(0); } public GraphiteClient getGraphiteClient() { return graphiteClient; } public void setGraphiteClient(GraphiteClient graphiteClient) { this.graphiteClient = graphiteClient; } @Override public int getOrder() { return order; } public void setOrder(int theOrder) { order = theOrder; } }