001/**
002 * The MIT License (MIT)
003 *
004 * Copyright (c) 2019 nobark (tools4j), Marco Terzer, Anton Anufriev
005 *
006 * Permission is hereby granted, free of charge, to any person obtaining a copy
007 * of this software and associated documentation files (the "Software"), to deal
008 * in the Software without restriction, including without limitation the rights
009 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010 * copies of the Software, and to permit persons to whom the Software is
011 * furnished to do so, subject to the following conditions:
012 *
013 * The above copyright notice and this permission notice shall be included in all
014 * copies or substantial portions of the Software.
015 *
016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
017 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
018 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
019 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
020 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
021 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
022 * SOFTWARE.
023 */
024package org.tools4j.nobark.run;
025
026import java.util.Objects;
027import java.util.concurrent.ThreadFactory;
028
029import sun.misc.Contended;
030
031/**
032 * A thread that performs a {@link java.lang.Runnable runnable} in a new thread.
033 * The thread is started immediately upon construction and it can be stopped via stop or auto-close method.
034 */
035public class StoppableThread implements ThreadLike {
036    private final RunnableFactory runnableFactory;
037    private final Thread thread;
038    @Contended
039    private volatile boolean running;
040
041    /**
042     * Constructor for stoppable thread; it is recommended to use the static start(..) methods instead.
043     *
044     * @param runnableFactory   the factory for the runnable;
045     *                          the <i>{@link #keepRunning}</i> condition is passed to the factory as lambda
046     * @param threadFactory     the factory to provide the thread
047     */
048    protected StoppableThread(final RunnableFactory runnableFactory, final ThreadFactory threadFactory) {
049        this.thread = threadFactory.newThread(this::run);
050        this.runnableFactory = Objects.requireNonNull(runnableFactory);
051        this.running = true;
052        thread.start();
053    }
054
055    /**
056     * Creates, starts and returns a new shutdownable thread.
057     *
058     * @param runnableFactory   the factory for the runnable;
059     *                          the <i>{@link #keepRunning}</i> condition is passed to the factory as lambda
060     * @param threadFactory     the factory to provide the thread
061     * @return the newly created and started stoppable thread
062     */
063    public static StoppableThread start(final RunnableFactory runnableFactory, final ThreadFactory threadFactory) {
064        return new StoppableThread(runnableFactory, threadFactory);
065    }
066
067    private void run() {
068        final Runnable runnable = runnableFactory.create(this::keepRunning);
069        runnable.run();
070    }
071
072    private boolean keepRunning() {
073        return running;
074    }
075
076    @Override
077    public Thread.State threadState() {
078        return thread.getState();
079    }
080
081    @Override
082    public void stop() {
083        running = false;
084    }
085
086    @Override
087    public void join(final long millis) {
088        try {
089            thread.join(millis);
090        } catch (final InterruptedException e) {
091            throw new IllegalStateException("Join interrupted for thread " + thread);
092        }
093    }
094
095    @Override
096    public String toString() {
097        return thread.getName();
098    }
099}