001/**
002 * The MIT License (MIT)
003 *
004 * Copyright (c) 2018 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.loop;
025
026import sun.misc.Contended;
027
028import java.util.Objects;
029import java.util.concurrent.ThreadFactory;
030import java.util.function.BooleanSupplier;
031import java.util.function.Function;
032
033/**
034 * A thread that performs a {@link java.lang.Runnable runnable} in a new thread.
035 * The thread is started immediately upon construction and it can be stopped via stop or auto-close method.
036 */
037public class StoppableThread implements Stoppable {
038    private final Function<BooleanSupplier, Runnable> runnableFactory;
039    private final Thread thread;
040    @Contended
041    private volatile boolean running;
042
043    /**
044     * Constructor for stoppable thread; it is recommended to use the static start(..) methods instead.
045     *
046     * @param runnableFactory   the factory for the runnable;
047     *                          the <i>{@link #isRunning}</i> condition is passed to the factory as lambda
048     * @param threadFactory     the factory to provide the thread
049     */
050    protected StoppableThread(final Function<BooleanSupplier, Runnable> runnableFactory,
051                              final ThreadFactory threadFactory) {
052        this.thread = threadFactory.newThread(this::run);
053        this.runnableFactory = Objects.requireNonNull(runnableFactory);
054        this.running = true;
055        thread.start();
056    }
057
058    /**
059     * Creates, starts and returns a new shutdownable thread.
060     *
061     * @param runnableFactory   the factory for the runnable;
062     *                          the <i>{@link #isRunning}</i> condition is passed to the factory as lambda
063     * @param threadFactory     the factory to provide the thread
064     * @return the newly created and started stoppable thread
065     */
066    public static StoppableThread start(final Function<BooleanSupplier, Runnable> runnableFactory,
067                                        final ThreadFactory threadFactory) {
068        return new StoppableThread(runnableFactory, threadFactory);
069    }
070
071    private void run() {
072        final Runnable runnable = runnableFactory.apply(this::isRunning);
073        runnable.run();
074    }
075
076    public boolean isRunning() {
077        return running;
078    }
079
080    @Override
081    public void stop() {
082        running = false;
083    }
084
085    public void join() {
086        join(0);
087    }
088
089    public void join(final long millis) {
090        try {
091            thread.join(millis);
092        } catch (final InterruptedException e) {
093            throw new IllegalStateException("join was interrupted for thread=" + thread);
094        }
095    }
096
097    @Override
098    public String toString() {
099        return thread.getName();
100    }
101}