The business requirements are as follows: accept a large amount of performance data, require multi-threaded processing of performance data, and only one of the same performance data can be processed at any time.
There are 5 classes here:
ProcessScheduler: Entry for accepting performance data and adding each piece of performance data to the queue for processing
ActionExecutor: Thread pool wrapper class
ActionQueue: task queue class, used to save tasks of the same performance, ensure thread safety, and only one task in the queue is processed at one time
ProcessAction: task class, each performance task is packaged into a task, and the business logic for data processing is in this class
ActionCommand: command class, implements Runnable interface, wraps task class for thread pool processing
The following code is presented in the most concise way
public final class ActionQueue { //The internal implementation of the queue, in fact, it is also possible to use the queue private final List<ProcessAction> runQueue = new ArrayList<ProcessAction>(); //Lock the object to ensure that it can only be stored or retrieved at the same time private final Object lockQueue = new Object(); //true means that the same kind of task in this queue is being processed public boolean IS_RUNNING = false; public ActionQueue(ProcessAction action) { addProcessAction(action); } public void addProcessAction(ProcessAction action) { synchronized (lockQueue) { runQueue.add(action); } } // If no such task is being processed at this time, get an executable action from actionQueue, otherwise return null public ProcessAction borrowOneCanExecuteAction() { synchronized (lockQueue) { if (!IS_RUNNING && runQueue.size()>0) { ProcessAction action = runQueue.get(0); runQueue.remove(0); IS_RUNNING = true; return action; } return null; } } }
public final class ProcessAction { //Data to be processed by the task private RawData data; //identification of the same data private String location; public ProcessAction(RawData data) { this.data = data; this.location = data.getLocation(); } public void execute() { //Here is the business logic of data processing } }
public final class ActionCommand implements Runnable { //The same kind of task is in the queue private final ActionQueue queue; //wrapped task private final ProcessAction action; public ActionCommand(ProcessAction action, ActionQueue queue) { this.action = action; this.queue = queue; } public void run() { //Process the internal logic of the task, set the queue IS_RUNNING flag to false after completion, and then call the method to process the next task of the same type of this task action.execute(); queue.IS_RUNNING = false; ProcessScheduler.instance().exeActionByLocation(action.getLocation()); } }
public final class ProcessScheduler { // singleton private static ProcessScheduler inst = new ProcessScheduler(); //Thread-safe Map, the key value is the identifier of the same task, and the value value is the queue that stores the same task private final ConcurrentHashMap<String, ActionQueue> actionQueueMap = new ConcurrentHashMap<String, ActionQueue>(); private final ActionExecutor actionExecutor = new ActionExecutor(); private boolean bStart = false; public static ProcessScheduler instance() { return inst; } private ProcessScheduler() { } public void process(List<RawData> datas) { for (RawData data : datas) { String location = data.getLocation(); ProcessAction action = new ProcessAction(data); addAction(location, action); } } private void addAction(String location, ProcessAction action) { if (location == null || action == null) { return; } if (!bStart) { start(); } //Get the queue to which the task belongs, add the task to the queue, and process a task in the queue ActionQueue queueFind = actionQueueMap.putIfAbsent(location,new ActionQueue()); if(queueFind == null) { queueFind = actionQueueMap.putIfAbsent(location, new ActionQueue()); } queueFind.addProcessAction(action); processQueue(queueFind); } // Get the queue according to the location from the task queue warehouse, and try to process a processable task in the queue public void exeActionByLocation(String location) { LOGGER.debug("Performs Process ProcessScheduler exeActionBylocation :{}",location); ActionQueue queueFind = actionQueueMap.get(location); if (queueFind == null) { return; } processQueue(queueFind); } // Try to take a task from the task queue and put it into the thread pool for processing private void processQueue(ActionQueue queue) { ProcessAction exeAction = queue.borrowOneCanExecuteAction(); if (exeAction != null) { actionExecutor.executeAction(exeAction, queue); } } //Set the state bStart to true to ensure that the start method only executes the start method that calls actionExecutor once private synchronized void start() { if (bStart) { return; } bStart = true; this.actionExecutor.start(); } }
public final class ActionExecutor { private static final int THREAD_KEEPLIVE_MINUTE = 10; private int corePoolSize = 10; private int maxCurrentNum = 45; private final BlockingQueue<ActionCommand> actionList; private ThreadPoolExecutor executor = null; //This is not necessary, because only the start method of ProcessScheduler calls the start method here, and it is guaranteed to be called only once outside private boolean isStart = false; //Alternatively not, ProcessScheduler guarantees that tasks will only be dropped after the thread pool starts private final Object lockStart = new Object(); public ActionExecutor() { this.actionList = new LinkedBlockingQueue<ActionCommand>(); } //Process the task class, wrap it as a command class, and put it in the thread pool public void executeAction(ProcessAction action, ActionQueue queue) { ActionCommand command = new ActionCommand(action, queue); synchronized (lockStart) { executor.execute(command); } } //Instantiate the thread pool object @SuppressWarnings({ "rawtypes", "unchecked" }) public void start() { synchronized (lockStart) { if (isStart) { return; } BlockingQueue blockingQueue = new LinkedBlockingQueue(); RejectedExecutionHandler handler = new ThreadPoolExecutor.DiscardPolicy() { public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { ActionExecutor.LOGGER.error("rejected Runnable Execution {}", r); } }; this.executor = new ThreadPoolExecutor(corePoolSize, maxCurrentNum, THREAD_KEEPLIVE_MINUTE, TimeUnit.MINUTES, blockingQueue, handler); this.executor.setThreadFactory(new SchedulerThreadFactory()); this.isStart = true; } } // The static inner class is used to wrap the new thread, the main function is to set the thread name, priority and set the new thread as a non-daemon thread static class SchedulerThreadFactory implements ThreadFactory { private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1); private final AtomicInteger threadNumber = new AtomicInteger(1); private final String namePrefix; public SchedulerThreadFactory() { this.namePrefix = "PerformanceProcessPool-" + POOL_NUMBER.getAndIncrement() + "-thread-"; } @Override public Thread newThread(Runnable r) { Thread t = new Thread(r, this.namePrefix + this.threadNumber.getAndIncrement()); if (t.isDaemon()) { t.setDaemon(false); } return t; } } }
This is the processing of this business requirement, I have cut it out