/* * @(#)StartAwareFutureTask.java 2012-8-1 下午10:00:00 * * Copyright (c) 2011-2012 Makersoft.org all rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * */ package org.makersoft.shards.strategy.access.impl; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; /** * Extension of FutureTask that provides slightly different cancel() * behavior. We want cancel() to only return true if the task has not yet run. * * Multi-threaded scenario 1: * run() invoked in T1 * The task hasn't been cancelled, so runCalled is set * to true. This happens in a synchronized block so cancel() cannot * execute while the flag is being set. Once we enter the synchronized * block and get past the cancelled check we are guaranteed to run, and * if cancel() is invoked at any point afterwards runCalled will be true, so * cancel() will be unable to return anything other than false, which is what * we want. * * Multi-threaded scenario 2: * cancel() invoked in T1 * The method is synchronized, so even if T2 invokes run() it won't be able to * enter the synchronized block until cancel() finishes executing. * The cancelled flag is set to true, so we return right away. * cancel() returns the result of super.cancel() because runCalled is guaranteed to be false. * */ class StartAwareFutureTask extends FutureTask<Void> { boolean runCalled; boolean cancelled; private final int id; private final Log log = LogFactory.getLog(getClass()); public StartAwareFutureTask(Callable<Void> callable, int id) { super(callable); this.id = id; } @Override public void run() { log.debug(String.format("Task %d: Run invoked.", id)); synchronized(this) { if (cancelled) { log.debug(String.format("Task %d: Task will not run.", id)); return; } runCalled = true; } log.debug(String.format("Task %d: Task will run.", id)); super.run(); } @Override public synchronized boolean cancel(boolean mayInterruptIfRunning) { if(runCalled) { /** * If run has already been called we can't call super. That's because * super.cancel might be called in between the time we leave the * synchronization block in run() and the time we call super.run(). * super.run() checks the state of the FutureTask before actuall invoking * the inner task, and if that check sees that this task is cancelled it * won't run. That leaves us in a position where a task actually has * been cancelled but cancel returns true, so we're left with a counter * that never gets decremented and everything hangs. */ return false; } boolean result = superCancel(mayInterruptIfRunning); cancelled = true; log.debug(String.format("Task %d: Task cancelled.", id)); return result; } public int getId() { return id; } boolean superCancel(boolean mayInterruptIfRunning) { return super.cancel(mayInterruptIfRunning); } }