/** * Copyright (c) 2014-2017 by the respective copyright holders. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html */ package org.eclipse.smarthome.core.common; import java.util.Map; import java.util.Map.Entry; import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; 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; /** * <p> * This class provides a general mechanism to create thread pools. In general, no code of Eclipse SmartHome * should deal with its own pools, but rather use this class. * The created thread pools have named threads, so that it is easy to find them in the debugger. Additionally, it is * possible to configure the pool sizes through the configuration admin service, so that solutions have the chance to * tweak the pool sizes according to their needs. * </p> * <p> * The configuration can be done as * <br/> * {@code org.eclipse.smarthome.threadpool:<poolName>=<poolSize>} * <br/> * All threads will time out after {@link THREAD_TIMEOUT}. * </p> * * @author Kai Kreuzer - Initial contribution * */ public class ThreadPoolManager { private final static Logger logger = LoggerFactory.getLogger(ThreadPoolManager.class); protected static final int DEFAULT_THREAD_POOL_SIZE = 5; protected static final long THREAD_TIMEOUT = 65L; protected static final long THREAD_MONITOR_SLEEP = 60000; static protected Map<String, ExecutorService> pools = new WeakHashMap<>(); static private Map<String, Integer> configs = new ConcurrentHashMap<>(); protected void activate(Map<String, Object> properties) { modified(properties); } protected void modified(Map<String, Object> properties) { for (Entry<String, Object> entry : properties.entrySet()) { if (entry.getKey().equals("service.pid") || entry.getKey().equals("component.id") || entry.getKey().equals("component.name")) { continue; } String poolName = entry.getKey(); Object config = entry.getValue(); if (config == null) { configs.remove(poolName); } if (config instanceof String) { try { Integer poolSize = Integer.valueOf((String) config); configs.put(poolName, poolSize); ThreadPoolExecutor pool = (ThreadPoolExecutor) pools.get(poolName); if (pool instanceof ScheduledThreadPoolExecutor) { pool.setCorePoolSize(poolSize); logger.debug("Updated scheduled thread pool '{}' to size {}", new Object[] { poolName, poolSize }); } else if (pool instanceof QueueingThreadPoolExecutor) { pool.setMaximumPoolSize(poolSize); logger.debug("Updated queuing thread pool '{}' to size {}", new Object[] { poolName, poolSize }); } } catch (NumberFormatException e) { logger.warn("Ignoring invalid configuration for pool '{}': {} - value must be an integer", new Object[] { poolName, config }); continue; } } } } /** * Returns an instance of a scheduled thread pool service. If it is the first request for the given pool name, the * instance is newly created. * * @param poolName a short name used to identify the pool, e.g. "discovery" * @return an instance to use */ static public ScheduledExecutorService getScheduledPool(String poolName) { ExecutorService pool = pools.get(poolName); if (pool == null) { synchronized (pools) { // do a double check if it is still null or if another thread might have created it meanwhile pool = pools.get(poolName); if (pool == null) { int cfg = getConfig(poolName); pool = Executors.newScheduledThreadPool(cfg, new NamedThreadFactory(poolName)); ((ThreadPoolExecutor) pool).setKeepAliveTime(THREAD_TIMEOUT, TimeUnit.SECONDS); ((ThreadPoolExecutor) pool).allowCoreThreadTimeOut(true); pools.put(poolName, pool); logger.debug("Created scheduled thread pool '{}' of size {}", new Object[] { poolName, cfg }); } } } if (pool instanceof ScheduledExecutorService) { return (ScheduledExecutorService) pool; } else { throw new IllegalArgumentException("Pool " + poolName + " is not a scheduled pool!"); } } /** * Returns an instance of a cached thread pool service. If it is the first request for the given pool name, the * instance is newly created. * * @param poolName a short name used to identify the pool, e.g. "discovery" * @return an instance to use */ static public ExecutorService getPool(String poolName) { ExecutorService pool = pools.get(poolName); if (pool == null) { synchronized (pools) { // do a double check if it is still null or if another thread might have created it meanwhile pool = pools.get(poolName); if (pool == null) { int cfg = getConfig(poolName); pool = QueueingThreadPoolExecutor.createInstance(poolName, cfg); ((ThreadPoolExecutor) pool).setKeepAliveTime(THREAD_TIMEOUT, TimeUnit.SECONDS); ((ThreadPoolExecutor) pool).allowCoreThreadTimeOut(true); pools.put(poolName, pool); logger.debug("Created thread pool '{}' with size {}", new Object[] { poolName, cfg }); } } } return pool; } protected static int getConfig(String poolName) { Integer cfg = configs.get(poolName); return (cfg != null) ? cfg : DEFAULT_THREAD_POOL_SIZE; } /** * This is a normal thread factory, which adds a named prefix to all created threads. */ protected static class NamedThreadFactory implements ThreadFactory { protected final ThreadGroup group; protected final AtomicInteger threadNumber = new AtomicInteger(1); protected final String namePrefix; protected final String name; public NamedThreadFactory(String threadPool) { this.name = threadPool; this.namePrefix = "ESH-" + threadPool + "-"; SecurityManager s = System.getSecurityManager(); group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup(); } @Override public Thread newThread(Runnable r) { Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0); if (!t.isDaemon()) { t.setDaemon(true); } if (t.getPriority() != Thread.NORM_PRIORITY) { t.setPriority(Thread.NORM_PRIORITY); } return t; } public String getName() { return name; } } }