/** * Copyright (c) 2015 unfoldingWord * http://creativecommons.org/licenses/MIT/ * See LICENSE file for details. * Contributors: * PJ Fechner <pj@actsmedia.com> */ package services; import java.util.HashMap; import java.util.Map; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import model.parsers.MediaType; /** * Created by PJ Fechner on 8/21/15. * Class to manage a thread pool in order to make full use of all cores during a data update. */ public class UpdateManager { private static final String TAG = "UpdateManager"; // Sets the amount of time an idle thread will wait for a task before terminating private static final int KEEP_ALIVE_TIME = 100; // Sets the Time Unit to seconds private static final TimeUnit KEEP_ALIVE_TIME_UNIT; static { KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS; } // Sets the initial thread pool size to 8 private static final int CORE_POOL_SIZE = 2; // Sets the maximum thread pool size to 8 private static final int MAXIMUM_POOL_SIZE = 2; // A managed pool of background decoder threads private final Map<Long, Map<MediaType, ThreadPoolExecutor>> updateThreadPools; private static final int unRelatedQueueId = -1; private static final MediaType unrelatedMediaType = MediaType.MEDIA_TYPE_NONE; /** * NOTE: This is the number of total available cores. On current versions of * Android, with devices that use plug-and-play cores, this will return less * than the total number of cores. The total number of cores is not * available in current Android implementations. */ private static int NUMBER_OF_CORES = 2;// Runtime.getRuntime().availableProcessors(); private static UpdateManager ourInstance = new UpdateManager(); //region adding /** * Will add the passed runnable to the thread pool * @param runnable runnable to add * @param id id associate with this threadpool */ synchronized static public void addRunnable(Runnable runnable, long id, MediaType type){ // Log.d(TAG, "Runnable will be added to index: " + index); getInstance().getPool(id, type).execute(runnable); } /** * Will add the passed runnable to the thread pool * @param runnable runnable to add */ static public void addRunnable(Runnable runnable){ // Log.d(TAG, "Runnable will be added to index: " + index); addRunnable(runnable, unRelatedQueueId, unrelatedMediaType); } //endregion static public void haltQueue(){ getInstance().forceHaltQueue(unRelatedQueueId, unrelatedMediaType); } static synchronized public void haltQueue(long id, MediaType type){ getInstance().forceHaltQueue(id, type); } static public boolean queueIsActive(){ return queueIsActive(unRelatedQueueId, unrelatedMediaType); } static public boolean queueIsActive(long id, MediaType type){ return getInstance().isQueueActive(id, type); } private static UpdateManager getInstance() { if(ourInstance == null){ ourInstance = new UpdateManager(); } return ourInstance; } private UpdateManager() { updateThreadPools = new HashMap<>(); } private boolean isQueueActive(long id, MediaType type){ if(!updateThreadPools.containsKey(id) || !updateThreadPools.get(id).containsKey(type)){ // Log.d(TAG, "queue isn't active because there it doesn't exist in the pool"); return false; } int active = updateThreadPools.get(id).get(type).getActiveCount(); // Log.d(TAG, "queue with number active: " + active + " for id: " + id + " and type: " + type.toString()); return active > 1; } private int forceHaltQueue(long id, MediaType type){ if(!updateThreadPools.containsKey(id) || !updateThreadPools.get(id).containsKey(type)){ return -1; } else{ int numberHalted = updateThreadPools.get(id).get(type).shutdownNow().size(); updateThreadPools.get(id).remove(type); return numberHalted; } } public static long getActiveNumber(){ long total = 0; for (Map.Entry<Long, Map<MediaType, ThreadPoolExecutor>> versionEntry : getInstance().updateThreadPools.entrySet()) { for(Map.Entry<MediaType, ThreadPoolExecutor> typeEntry : versionEntry.getValue().entrySet()){ total += typeEntry.getValue().getTaskCount(); } } return total; } public static void haltAllThreads(){ for (Map.Entry<Long, Map<MediaType, ThreadPoolExecutor>> versionEntry : getInstance().updateThreadPools.entrySet()) { for(Map.Entry<MediaType, ThreadPoolExecutor> typeEntry : versionEntry.getValue().entrySet()){ typeEntry.getValue().shutdownNow(); } } ourInstance = null; } /** * @param id id of the ThreadPool to use * @return ThreadPool matching the passed id */ private synchronized ThreadPoolExecutor getPool(long id, MediaType type){ if(!updateThreadPools.containsKey(id)){ updateThreadPools.put(id, new HashMap<MediaType, ThreadPoolExecutor>()); } if(!updateThreadPools.get(id).containsKey(type)){ updateThreadPools.get(id).put(type, getNewThreadExecutor()); } return updateThreadPools.get(id).get(type); } private ThreadPoolExecutor getNewThreadExecutor(){ return new ThreadPoolExecutor((NUMBER_OF_CORES < 2)? 2 : NUMBER_OF_CORES, (NUMBER_OF_CORES < 2)? 2 : NUMBER_OF_CORES, KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT, new LinkedBlockingQueue<Runnable>()); } }