001 /**
002 * Copyright 2004-2012 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.common.threads;
017
018 import java.lang.Thread.UncaughtExceptionHandler;
019 import java.util.List;
020
021 import org.kuali.common.threads.listener.ProgressNotifier;
022
023 /**
024 * Produce ThreadHandlers that create and control execution of threads for iterating over List<T> objects
025 */
026 public class ThreadHandlerFactory {
027
028 /**
029 * Return an array of int's that represents as even of a split as possible
030 *
031 * For example: passing in 100,7 returns 15, 15, 14, 14, 14, 14, 14
032 *
033 * @param numerator
034 * @param denominator
035 * @return
036 */
037 protected int[] getDivideEvenly(int numerator, int denominator) {
038 int quotient = numerator / denominator;
039 int remainder = numerator % denominator;
040
041 int[] lengths = new int[denominator];
042 for (int i = 0; i < denominator; i++) {
043 int length = i < remainder ? quotient + 1 : quotient;
044 lengths[i] = length;
045 }
046 return lengths;
047 }
048
049 /**
050 * Given some context, produce a ThreadHandler for iterating over the list of elements provided in the context
051 *
052 * @param <T>
053 * @param context
054 * @return
055 */
056 public <T> ThreadHandler<T> getThreadHandler(ThreadHandlerContext<T> context) {
057 // Extract some local variables for convenience
058 List<T> list = context.getList();
059 int elements = list.size();
060 int max = context.getMax();
061 int min = context.getMin();
062
063 // Calculate # of threads and elements per thread
064 int threadCount = getThreadCount(max, min, elements, context.getDivisor());
065 int[] lengths = getDivideEvenly(elements, threadCount);
066
067 // Create a new thread handler
068 ThreadHandler<T> handler = new ThreadHandler<T>();
069 handler.setThreadCount(threadCount);
070
071 // Setup a notifier/listener for tracking progress
072 ProgressNotifier<T> notifier = new ProgressNotifier<T>();
073 notifier.setListener(context.getListener());
074 notifier.setTotal(list.size());
075 handler.setNotifier(notifier);
076
077 // Create a thread group and threads
078 ThreadGroup group = new ThreadGroup("List Iterator Threads");
079 group.setDaemon(true);
080 Thread[] threads = getThreads(handler, list, context.getHandler(), lengths);
081
082 // Store both in the handler
083 handler.setGroup(group);
084 handler.setThreads(threads);
085
086 // Return the ThreadHandler
087 return handler;
088 }
089
090 /**
091 * Generate threads where each thread iterates over a portion of the list
092 *
093 * @param <T>
094 * @param threadHandler
095 * @param list
096 * @param elementHandler
097 * @return
098 */
099 protected <T> Thread[] getThreads(ThreadHandler<T> thandler, List<T> list, ElementHandler<T> ehandler, int[] lengths) {
100 // Total threads we'll need
101 Thread[] threads = new Thread[thandler.getThreadCount()];
102
103 // Create each thread
104 int offset = 0;
105 for (int i = 0; i < threads.length; i++) {
106
107 // Get an identifier for this thread
108 int id = i + 1;
109
110 // The number of elements this thread needs to iterate over
111 int length = lengths[i];
112
113 // Give this thread some context
114 ListIteratorContext<T> context = new ListIteratorContext<T>(id, offset, length, list);
115 context.setNotifier(thandler.getNotifier());
116 context.setThreadHandler(thandler);
117 context.setElementHandler(ehandler);
118
119 // Create a thread and store it in the array
120 Runnable runnable = new ListIteratorThread<T>(context);
121 threads[i] = getThread(runnable, id, thandler.getGroup(), thandler);
122
123 // Increment offset
124 offset += length;
125 }
126
127 // Return an array of threads ready to execute
128 return threads;
129 }
130
131 /**
132 * Construct a Thread from the information provided
133 *
134 * @param <T>
135 * @param runnable
136 * @param id
137 * @param group
138 * @param handler
139 * @return
140 */
141 protected <T> Thread getThread(Runnable runnable, int id, ThreadGroup group, UncaughtExceptionHandler handler) {
142 Thread thread = new Thread(group, runnable, "ListIterator-" + id);
143 thread.setUncaughtExceptionHandler(handler);
144 thread.setDaemon(true);
145 return thread;
146 }
147
148 /**
149 * Get the number of threads to use. The number returned here will never be greater than max. It may be less than
150 * min, but only in the case where elements is also less than min.
151 *
152 * @param max
153 * @param min
154 * @param elements
155 * @param divisor
156 * @return
157 */
158 protected int getThreadCount(int max, int min, int elements, int divisor) {
159 // Reset min to max if needed
160 min = (min > max) ? max : min;
161
162 // Validate the params
163 validate(max, min, elements, divisor);
164
165 // Reduce max if appropriate
166 max = (max > elements) ? elements : max;
167
168 // Reduce min if appropriate
169 min = (min > elements) ? elements : min;
170
171 // Divisor allows clients to scale threads in proportion to the number of elements in the list
172 int threads = (divisor > 0) ? (elements / divisor) : max;
173
174 // Reset to max if we have exceeded it
175 threads = (threads > max) ? max : threads;
176
177 // Reset to min if we have dropped below it
178 threads = (threads < min) ? min : threads;
179
180 // Return the thread count to use
181 return threads;
182 }
183
184 protected void validate(int max, int min, int elements, int divisor) {
185 StringBuilder sb = new StringBuilder();
186 if (divisor < 0) {
187 sb.append(" [divisor must be >= 0] ");
188 }
189 if (max < 1) {
190 sb.append(" [max must be >= 1] ");
191 }
192 if (min < 0) {
193 sb.append(" [min must be >= 0] ");
194 }
195 if (elements < 0) {
196 sb.append(" [elements must be >= 0] ");
197 }
198 if (min > max) {
199 sb.append(" [min must be <= max] ");
200 }
201 if (sb.length() > 0) {
202 throw new IllegalArgumentException(sb.toString());
203 }
204 }
205
206 }