oddjob之smooth关闭程序
java程序的smooth关闭策略可以采用hook跟观察者的模式实现
无限等状态,如果状态出现可以关闭的事件则进行关闭
虚拟机的关闭通过钩子调用关闭,如果关闭失败,在超时时间内强制杀掉jvm
状态类
public interface State { public boolean isStoppable(); }
package com.s.stat; /** * A Stateful job implements this interface so that * it's state can be discovered. State is used to * control the flow of execution within Oddjob, as * well as being a way of informing client applications * of progress. * * @author Rob Gordon */ public interface Stateful { /** * Add a job state listener. * * @param listener The listener. * */ public void addStateListener(StateListener listener) ; /** * Remove a job state listener. * * @param listener The listener. */ public void removeStateListener(StateListener listener); /** * Get the last state event. * * @return The last State Event */ public StateEvent lastStateEvent(); }
package com.s.stat; import java.io.Serializable; import java.util.Date; import java.util.EventObject; /** * An instance of this class is produced when a job state changes. It is * passed to all JobStateListeners. * * @author Rob Gordon */ public class StateEvent extends EventObject implements Serializable { private static final long serialVersionUID = 20051026; static final String REPLACEMENT_EXCEPTION_TEXT = "Exception is not serializable, message is: "; private State state; private Date time; private Throwable exception; /** * Used to replace a non serializable exception. * */ class ExceptionReplacement extends Exception { private static final long serialVersionUID = 20051217; public ExceptionReplacement(Throwable replacing) { super(REPLACEMENT_EXCEPTION_TEXT + replacing.getMessage()); super.setStackTrace(exception.getStackTrace()); } } /** * Constructor. * * @param job The source of the event. * @param jobState The state. * @param time the Time of the event. * @param exception The exception if applicable, or null otherwise. */ public StateEvent(Stateful job, State jobState, Date time, Throwable exception) { super(job); if (jobState == null) { throw new NullPointerException("JobState can not be null!"); } this.state = jobState; this.time = time; this.exception = exception; } /** * Constructor. * * @param job The source of the event. * @param jobState The state. * @param exception The exception if applicable, or null otherwise. */ public StateEvent(Stateful job, State jobState, Throwable exception) { this(job, jobState, new Date(), exception); } /** * Constructor. * * @param job The source of the event. * @param jobState The state. */ public StateEvent(Stateful job, State jobState) { this(job, jobState, null); } @Override public Stateful getSource() { return (Stateful) super.getSource(); } /** * Get the job state. * * @return The job state. */ public State getState() { return state; } /** * Get the exception if applicable, null otherwise. * * @return The exception of null. */ public Throwable getException() { return exception; } /** * Get the time of the event.. * * @return The time. */ public Date getTime() { return time; } /** * Override toString. */ public String toString() { return "JobStateEvent, source=" + getSource() + ", " + state; } }
package com.s.stat; /** * Implementors of this interface are able to listen to state events. * * @author Rob Gordon */ public interface StateListener { /** * Triggered when the job state changes. * * @param event The job state event. */ public void jobStateChange(StateEvent event); }
package com.s; import com.s.stat.StateEvent; import com.s.stat.StateListener; import com.s.stat.Stateful; public class Runner implements Runnable{ public static final String KILLER_TIMEOUT_PROPERTY = "shutdown.killer.timeout"; public static final long DEFAULT_KILLER_TIMEOUT = 15000L; /** Flag if program is being destroyed from the Shutdown Hook. */ private volatile boolean destroying = false; /** The killer thread time out. */ private final long killerTimeout; public Runner() { String timeoutProperty = System.getProperty(KILLER_TIMEOUT_PROPERTY); if (timeoutProperty == null) { killerTimeout = DEFAULT_KILLER_TIMEOUT; } else { killerTimeout = Long.parseLong(timeoutProperty); } } @Override public void run() { // TODO Auto-generated method stub Runtime.getRuntime().addShutdownHook(new ShutdownHook()); // Possibly wait for Oddjob to be in a stopped state. new StopWait(new Stateful() { @Override public void removeStateListener(StateListener listener) { // TODO Auto-generated method stub } @Override public StateEvent lastStateEvent() { // TODO Auto-generated method stub return null; } @Override public void addStateListener(StateListener listener) { // TODO Auto-generated method stub } }, Long.MAX_VALUE).run(); // 调用程序的stop } /** * shutdown hook. * <p> * This Class has evolved quite a lot though trial and error due to * a lack of understanding of JVM shutdown. Should this thread be a * daemon? Current thinking is no because you don't want other daemon * threads to terminate until has been shutdown properly. * */ class ShutdownHook extends Thread { /** Killer thread will forcibly halt if it hasn't terminated * cleanly. */ private Thread killer; /* * (non-Javadoc) * @see java.lang.Thread#run() */ public void run() { // logger.info("Shutdown Hook Executing."); // killer will just kill process if we can't stop in 15 sec killer = new Thread(new Runnable() { public void run() { try { Thread.sleep(killerTimeout); } catch (InterruptedException e) { // logger.debug("Killer thread interrupted and terminating."); return; } // logger.error("Failed to stop Oddjob nicely, using halt(-1)"); Runtime.getRuntime().halt(-1); } }); // start the killer. Not sure it really need to be daemon but // it does no harm. // logger.debug("Starting killer thread."); killer.setDaemon(true); killer.start(); // 调用程序的关闭 // Nothing's hanging so we don't need our killer. //TODO 判断程序是否调用自身的关闭程序,如果成功则单端killer // killer.interrupt(); } } }
package com.s; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import com.s.stat.IsStoppable; import com.s.stat.State; import com.s.stat.StateEvent; import com.s.stat.StateListener; import com.s.stat.Stateful; /** * A utility class to provide wait until stopped functionality. * <p> * The default timeout is 5 seconds before a {@link FailedToStopException} * is thrown. * * @author rob * */ public class StopWait { private final Stateful stateful; private final long timeout; /** * Constructor with default timeout. * * @param stateful The thing to wait until stopped. */ public StopWait(Stateful stateful) { this(stateful, 5000); } /** * Constructor where timeout can be specified. * * @param stateful The thing to wait until stopped. * @param timeout The timeout. Note that a timeout of 0 or less is * no timeout. */ public StopWait(Stateful stateful, long timeout) { this.stateful = stateful; this.timeout = timeout; } /** * Run the stop wait. This will block until the job stops or the * timeout occurs. * * @throws FailedToStopException If timeout occurs. */ public void run() { if (new IsStoppable().test( stateful.lastStateEvent().getState())) { doWait(); } } private void doWait() { final BlockingQueue<State> handoff = new LinkedBlockingQueue<State>(); class StopListener implements StateListener { @Override public void jobStateChange(StateEvent event) { handoff.add(event.getState()); } }; StopListener listener = new StopListener(); stateful.addStateListener(listener); try { while (true) { State state = handoff.poll(timeout, TimeUnit.MILLISECONDS); if (state == null) { } if (!state.isStoppable()) { return; } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { stateful.removeStateListener(listener); } } }