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.concurrent.TimeUnit; 031import java.util.concurrent.atomic.AtomicInteger; 032import java.util.function.BooleanSupplier; 033import java.util.function.Function; 034 035/** 036 * A thread that performs a main {@link java.lang.Runnable runnable} in a new thread and another shutdown runnable the 037 * graceful {@link #shutdown} phase of the thread. The thread is started immediately upon construction. 038 */ 039public class ShutdownableThread implements Shutdownable { 040 041 private static final int RUNNING = 0; 042 private static final int SHUTDOWN = 1; 043 private static final int SHUTDOWN_NOW = 2; 044 private static final int TERMINATED = 4; 045 046 private final Function<BooleanSupplier, Runnable> mainRunnableFactory; 047 private final Function<BooleanSupplier, Runnable> shutdownRunnableFactory; 048 private final Thread thread; 049 @Contended 050 private final AtomicInteger state = new AtomicInteger(RUNNING); 051 052 /** 053 * Constructor for shutdownable thread; it is recommended to use the static start(..) methods instead. 054 * 055 * @param mainRunnableFactory the factory for the main runnable; 056 * the <i>{@link #isRunning}</i> condition is passed to the factory as lambda 057 * @param shutdownRunnableFactory the factory for the shutdown phase runnable; 058 * the <i>{@link #isShutdownRunning}</i> condition is passed to the factory as lambda 059 * @param threadFactory the factory to provide the thread 060 */ 061 protected ShutdownableThread(final Function<BooleanSupplier, Runnable> mainRunnableFactory, 062 final Function<BooleanSupplier, Runnable> shutdownRunnableFactory, 063 final ThreadFactory threadFactory) { 064 this.mainRunnableFactory = Objects.requireNonNull(mainRunnableFactory); 065 this.shutdownRunnableFactory = Objects.requireNonNull(shutdownRunnableFactory); 066 this.thread = threadFactory.newThread(this::run); 067 thread.start(); 068 } 069 070 /** 071 * Creates, starts and returns a new shutdownable thread. 072 * 073 * @param mainRunnableFactory the factory for the main runnable; 074 * the <i>{@link #isRunning}</i> condition is passed to the factory as lambda 075 * @param shutdownRunnableFactory the factory for the shutdown phase runnable; 076 * the <i>{@link #isShutdownRunning}</i> condition is passed to the factory as lambda 077 * @param threadFactory the factory to provide the thread 078 * @return the newly created and started shutdownable thread 079 */ 080 public static ShutdownableThread start(final Function<BooleanSupplier, Runnable> mainRunnableFactory, 081 final Function<BooleanSupplier, Runnable> shutdownRunnableFactory, 082 final ThreadFactory threadFactory) { 083 return new ShutdownableThread(mainRunnableFactory, shutdownRunnableFactory, threadFactory); 084 } 085 086 private void run() { 087 final Runnable main = mainRunnableFactory.apply(this::isRunning); 088 final Runnable shutdown = shutdownRunnableFactory.apply(this::isShutdownRunning); 089 main.run(); 090 shutdown.run(); 091 notifyTerminated(); 092 } 093 094 @Override 095 public void shutdown() { 096 state.compareAndSet(RUNNING, SHUTDOWN); 097 } 098 099 @Override 100 public void shutdownNow() { 101 final int shutdownAndNow = SHUTDOWN | SHUTDOWN_NOW; 102 if (!state.compareAndSet(RUNNING, shutdownAndNow)) { 103 state.compareAndSet(SHUTDOWN, shutdownAndNow); 104 } 105 } 106 107 private void notifyTerminated() { 108 if (!state.compareAndSet(SHUTDOWN, SHUTDOWN | TERMINATED)) { 109 state.compareAndSet(SHUTDOWN | SHUTDOWN_NOW, SHUTDOWN | SHUTDOWN_NOW | TERMINATED); 110 } 111 } 112 113 private boolean isRunning() { 114 return (state.get() & SHUTDOWN) == 0; 115 } 116 117 private boolean isShutdownRunning() { 118 return (state.get() & SHUTDOWN_NOW) == 0; 119 } 120 121 @Override 122 public boolean isShutdown() { 123 return (state.get() & SHUTDOWN) != 0; 124 } 125 126 @Override 127 public boolean isTerminated() { 128 return (state.get() & TERMINATED) != 0; 129 } 130 131 @Override 132 public boolean awaitTermination(final long timeout, final TimeUnit unit) { 133 if (timeout < 0) { 134 throw new IllegalArgumentException("timeout value is negative: " + timeout); 135 } 136 if (isTerminated()) { 137 return true; 138 } 139 if (timeout == 0) { 140 return isTerminated(); 141 } 142 final long millis = unit.toMillis(timeout); 143 final long nanos = unit.toNanos(timeout - unit.convert(millis, TimeUnit.MILLISECONDS)); 144 try { 145 if (nanos == 0) { 146 thread.join(millis); 147 } else { 148 thread.join(millis, (int) nanos); 149 } 150 } catch (final InterruptedException e) { 151 throw new IllegalStateException("Join interrupted for thread " + thread); 152 } 153 return isTerminated(); 154 } 155 156 /** 157 * Returns the name of the thread that was created with the thread factory passed to the constructor. 158 * 159 * @return the service thread's name 160 * @see Thread#getName() 161 */ 162 @Override 163 public String toString() { 164 return thread.getName(); 165 } 166}