package com.inter6.mail.module; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.inter6.mail.job.thread.ThreadSupportJob; /** * Worker 패턴의 ThreadPool 구현체<br> * <ul> * <li>기본 worker Thread 개수 : 1 ~ core*2</li> * <li>job 싸이클간 대기 시간 3초.</li> * <li>에러 발생시 대기 시간 10초.</li> * </ul> */ public class Workers { private final Logger log = LoggerFactory.getLogger(this.getClass()); private final static int CPU_CORE_CNT = Runtime.getRuntime().availableProcessors(); public final static int MAX_POOL_SIZE_DEFAULT = CPU_CORE_CNT * 2; private ThreadPoolExecutor workerPoolExecutor = null; private final RejectedExecutionHandler rejectedExecutionHandler = new BlockPolicy(); private final AtomicInteger currentPoolNumber = new AtomicInteger(0); private String workersName = null; private int corePoolSize = CPU_CORE_CNT / 4; private int maximumPoolSize = MAX_POOL_SIZE_DEFAULT; private BlockingQueue<Runnable> workQueue = null; private final AtomicInteger currentExecuteCnt = new AtomicInteger(); private int terminateTimeout = 3; private int keepAliveTime = 1; private TimeUnit timeUnit = TimeUnit.SECONDS; public void initailize(final String workersName) { if (this.workerPoolExecutor != null) { throw new IllegalStateException("worker pool already initialized !"); } this.workersName = workersName; this.workQueue = new LinkedBlockingQueue<>(Workers.MAX_POOL_SIZE_DEFAULT); this.workerPoolExecutor = this.createThreadPoolExecutor(workersName); } private ThreadPoolExecutor createThreadPoolExecutor(final String workersName) { ThreadFactory threadFactory = new WorkersFactory(workersName, this.currentPoolNumber.incrementAndGet()); this.currentExecuteCnt.set(0); ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(this.corePoolSize, this.maximumPoolSize, this.keepAliveTime, this.timeUnit, this.workQueue, threadFactory, this.rejectedExecutionHandler); this.log.info("new thread pool-" + this.currentPoolNumber.get() + " created!"); return threadPoolExecutor; } public void execute(ThreadSupportJob workerJob) { this.workerPoolExecutor.execute(workerJob); } public void setPoolSize(int corePoolSize, int maximumPoolSize) { this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; } public void setKeepAliveTime(int keepAliveTime, TimeUnit timeUnit) { this.keepAliveTime = keepAliveTime; this.timeUnit = timeUnit; } public void setTerminateTimeoutSeconds(int terminateTimeout) { this.terminateTimeout = terminateTimeout; } public void terminate() throws InterruptedException { if (this.workerPoolExecutor == null) { return; } this.log.info(this.getCurrentMonitoringInfo()); this.log.info(this.workersName + " terminate start[max:" + this.terminateTimeout + "sec]..."); this.workerPoolExecutor.shutdown(); this.workerPoolExecutor.awaitTermination(this.terminateTimeout, TimeUnit.SECONDS); this.log.info(this.getCurrentMonitoringInfo()); this.log.info(this.workersName + " terminate end"); } public void stop() { if (this.workerPoolExecutor == null) { return; } this.log.info(this.workersName + " start the stop..."); this.workQueue.clear(); int i = 0; do { try { Thread.sleep(1000); } catch (InterruptedException e) { // do nothing } this.log.info("waiting for " + ++i + " seconds to stop the walker.."); } while (this.isRun()); this.log.info(this.getCurrentMonitoringInfo()); this.log.info(this.workersName + " stopped [" + i + " sec elapsed]"); } public boolean isRun() { if (this.workerPoolExecutor == null) { return false; } if (this.workQueue.isEmpty()) { return this.workerPoolExecutor.getActiveCount() > 0; } return true; } public String getCurrentMonitoringInfo() { if (this.workerPoolExecutor == null) { return "this workers did not yet initialized !"; } return "\nWorkers name:" + this.workersName + "\nComplete task count:" + this.workerPoolExecutor.getCompletedTaskCount() + "\nCore worker count:" + this.workerPoolExecutor.getCorePoolSize() + "\nActive worker count:" + this.workerPoolExecutor.getActiveCount() + "\nMax worker count:" + this.workerPoolExecutor.getMaximumPoolSize() + "\nWorker keep alive time:" + this.workerPoolExecutor.getKeepAliveTime(TimeUnit.SECONDS) + "sec"; } /** * 큐가 꽉차면 Worker에게 job을 인계한 Thread가 queue에 빈 공간이 생길때 까지 block된다.<br> * 일반적인 Worker패턴에서는 Master Thread에서 worker의 일을 수행하지 않는다.<br> * java api에 존재하지 않아서 구현 - java 에서는 생산자/소비자 패턴만 지원 * * @author ysko */ private static class BlockPolicy implements RejectedExecutionHandler { private final Logger log = LoggerFactory.getLogger(this.getClass()); @Override public void rejectedExecution(Runnable job, ThreadPoolExecutor executor) { if (!executor.isShutdown()) { try { executor.getQueue().put(job); } catch (InterruptedException e) { this.log.error("block policy", e); } } } } /** * woker Thread생성 지원을 위한 Factory<br> * <ul> * <li>daemon thread</li> * <li>같은 thread group</li> * </ul> * * @author ysko */ private static class WorkersFactory implements ThreadFactory { private final Logger log = LoggerFactory.getLogger(this.getClass()); private final ThreadGroup threadGroup; private final AtomicInteger threadNumber = new AtomicInteger(1); private final String namePrefix; public WorkersFactory(String workerName, int poolNumber) { ThreadGroup parentThreadGroup = Thread.currentThread().getThreadGroup(); this.threadGroup = new ThreadGroup(parentThreadGroup, workerName); this.namePrefix = workerName + "Pool-" + poolNumber + "-Worker-"; } @Override public Thread newThread(Runnable runnable) { Thread thread = new Thread(this.threadGroup, runnable, this.namePrefix + this.threadNumber.getAndIncrement(), 0); this.log.info(thread.getName() + " created !"); if (!thread.isDaemon()) { thread.setDaemon(true); } return thread; } } }