001package com.pi4j.concurrent;
002
003/*
004 * #%L
005 * **********************************************************************
006 * ORGANIZATION  :  Pi4J
007 * PROJECT       :  Pi4J :: Java Library (Core)
008 * FILENAME      :  DefaultExecutorServiceFactory.java
009 *
010 * This file is part of the Pi4J project. More information about
011 * this project can be found here:  https://www.pi4j.com/
012 * **********************************************************************
013 * %%
014 * Copyright (C) 2012 - 2021 Pi4J
015 * %%
016 * This program is free software: you can redistribute it and/or modify
017 * it under the terms of the GNU Lesser General Public License as
018 * published by the Free Software Foundation, either version 3 of the
019 * License, or (at your option) any later version.
020 *
021 * This program is distributed in the hope that it will be useful,
022 * but WITHOUT ANY WARRANTY; without even the implied warranty of
023 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
024 * GNU General Lesser Public License for more details.
025 *
026 * You should have received a copy of the GNU General Lesser Public
027 * License along with this program.  If not, see
028 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
029 * #L%
030 */
031
032
033import java.util.ArrayList;
034import java.util.List;
035import java.util.concurrent.ExecutorService;
036import java.util.concurrent.Executors;
037import java.util.concurrent.ScheduledExecutorService;
038import java.util.concurrent.ThreadFactory;
039import java.util.concurrent.atomic.AtomicLong;
040
041public class DefaultExecutorServiceFactory implements ExecutorServiceFactory {
042
043    private static int MAX_THREADS_IN_POOL = 25;
044    private static List<ExecutorService> singleThreadExecutorServices = new ArrayList<>();
045
046    // this seemingly odd pattern is the recommended way to lazy-initialize static fields in effective java.
047    // The static "holder" class doesn't have it's static initializer called until it is accessed - and it's not accessed until the
048    // getInternalScheduledExecutor() method is called.
049    //
050    // (see effective java item 71:Use lazy initialization judiciously)
051    private static class ScheduledExecutorServiceHolder {
052        static final ScheduledExecutorService heldExecutor = Executors.newScheduledThreadPool(MAX_THREADS_IN_POOL, getThreadFactory("pi4j-scheduled-executor-%d"));
053    }
054    private static ScheduledExecutorService getInternalScheduledExecutorService() {
055        return ScheduledExecutorServiceHolder.heldExecutor;
056    }
057    private static class ScheduledExecutorServiceWrapperHolder {
058        static final ScheduledExecutorServiceWrapper heldWrapper = new ScheduledExecutorServiceWrapper(getInternalScheduledExecutorService());
059    }
060    private static ScheduledExecutorServiceWrapper getScheduledExecutorServiceWrapper() {
061        return ScheduledExecutorServiceWrapperHolder.heldWrapper;
062    }
063
064    // follow a similar lazy initialization pattern for the gpio events
065    private static class GpioEventExecutorServiceHolder {
066        static final ExecutorService heldExecutor = Executors.newCachedThreadPool(getThreadFactory("pi4j-gpio-event-executor-%d"));
067    }
068    private static ExecutorService getInternalGpioExecutorService() {
069        return GpioEventExecutorServiceHolder.heldExecutor;
070    }
071    private static class GpioEventExecutorServiceWrapperHolder {
072        static final ExecutorService heldWrapper = new ShutdownDisabledExecutorWrapper(getInternalGpioExecutorService());
073    }
074    private static ExecutorService getGpioEventExecutorServiceWrapper() {
075        return GpioEventExecutorServiceWrapperHolder.heldWrapper;
076    }
077
078    /**
079     * return an instance to the thread factory used to create new executor services
080     */
081    private static ThreadFactory getThreadFactory(final String nameFormat) {
082        final ThreadFactory defaultThreadFactory = Executors.privilegedThreadFactory();
083        return new ThreadFactory() {
084            final AtomicLong count = (nameFormat != null) ? new AtomicLong(0) : null;
085
086            @Override
087            public Thread newThread(Runnable runnable) {
088                Thread thread = defaultThreadFactory.newThread(runnable);
089                if (nameFormat != null) {
090                    thread.setName(String.format(nameFormat, count.getAndIncrement()));
091                }
092                return thread;
093            }
094        };
095    }
096
097    /**
098     * return an instance to the scheduled executor service (wrapper)
099     */
100    public ScheduledExecutorService getScheduledExecutorService() {
101        // we return the protected wrapper to prevent any consumers from
102        // being able to shutdown the scheduled executor service
103        return getScheduledExecutorServiceWrapper();
104    }
105
106    @Override
107    public ExecutorService getGpioEventExecutorService() {
108        // we return the protected wrapper to prevent any consumers from
109        // being able to shutdown the scheduled executor service
110        return getGpioEventExecutorServiceWrapper();
111    }
112
113    /**
114     * return a new instance of a single thread executor service
115     *
116     * This method is deprecated in favor of the getGpioEventExecutorService - which provides better guarantees around resource
117     * management
118     */
119    @Override
120    public ExecutorService newSingleThreadExecutorService() {
121
122        // create new single thread executor service
123        ExecutorService singleThreadExecutorService = Executors.newSingleThreadExecutor(getThreadFactory("pi4j-single-executor-%d"));
124
125        // add new instance to managed collection
126        singleThreadExecutorServices.add(singleThreadExecutorService);
127
128        // return new executor service
129        return singleThreadExecutorService;
130    }
131
132    /**
133     * shutdown executor threads
134     */
135    public void shutdown() {
136        // shutdown each single thread executor in the managed collection
137        for (ExecutorService singleThreadExecutorService : singleThreadExecutorServices) {
138            shutdownExecutor(singleThreadExecutorService);
139        }
140
141        // shutdown scheduled executor instance
142        shutdownExecutor(getInternalScheduledExecutorService());
143        shutdownExecutor(getInternalGpioExecutorService());
144
145    }
146
147    private void shutdownExecutor(ExecutorService executor) {
148        if (executor != null) {
149            if (!executor.isShutdown()) {
150                // this is a forceful shutdown;
151                // don't wait for the scheduled tasks to complete
152                executor.shutdownNow();
153            }
154        }
155    }
156}