All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mchange.v3.concurrent.BoundedExecutorService Maven / Gradle / Ivy

The newest version!
package com.mchange.v3.concurrent;

import java.util.*;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.TimeUnit;

import com.mchange.v2.log.MLevel;
import com.mchange.v2.log.MLog;
import com.mchange.v2.log.MLogger;

import static com.mchange.v3.concurrent.BoundedExecutorService.State.*;

// inspired by Java Concurrency In Practice, Goetz et al, Listing 8.4
// thanks to Julia Evans, http://jvns.ca/blog/2016/03/29/thread-pools-part-ii-i-love-blocking/
// for the tip!

// TODO: Seriously test this.

public final class BoundedExecutorService extends AbstractExecutorService {

    final static MLogger logger = MLog.getLogger( BoundedExecutorService.class );

    enum State { ACCEPTING, SATURATED, UNWINDING, SHUTDOWN, SHUTDOWN_NOW }

    //MT: Thread safe
    final ExecutorService inner;
    final int             blockBound;
    final int             restartBeneath;

    //MT: protected by this' lock
    State state;
    int   permits;

    Map waiters   = new HashMap();

    public BoundedExecutorService( ExecutorService inner, int blockBound, int restartBeneath )
    {
	if ( blockBound <= 0 || restartBeneath <= 0 )
	    throw new IllegalArgumentException( "blockBound and restartBeneath must both be greater than zero!" );
	if ( restartBeneath > blockBound )
	    throw new IllegalArgumentException( "restartBeneath must be less than or equal to blockBound!" );

	this.inner = inner;
	this.blockBound = blockBound;
	this.restartBeneath = restartBeneath;

	this.state   = ACCEPTING;
	this.permits = 0;
    } 

    public BoundedExecutorService( ExecutorService inner, int blockBound )
    { this( inner, blockBound, blockBound ); }

    public synchronized State getState()
    { return state; }

    public synchronized boolean isShutdown()
    { return state == SHUTDOWN || state == SHUTDOWN_NOW; }

    public synchronized boolean isTerminated()
    { return isShutdown() && permits == 0; }

    public synchronized void shutdown()
    {
	inner.shutdown();

	updateState( SHUTDOWN );
	this.notifyAll();
    }

    public synchronized List shutdownNow()
    {
	updateState( SHUTDOWN_NOW );

	List innerLeftovers = inner.shutdownNow();
	Collection ourLeftovers = waiters.values();

	List out = new ArrayList( innerLeftovers.size() + ourLeftovers.size() );
	out.addAll( innerLeftovers );
	out.addAll( ourLeftovers );

	// can't do it because -source 1.6 is still set
	//waiters.keySet().stream().forEach( t -> t.interrupt() );

	for ( Iterator ii = waiters.keySet().iterator(); ii.hasNext(); ) ii.next().interrupt();

	waiters.clear();
	    
	return Collections.unmodifiableList( out );
    }

    public synchronized boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException
    {
	long start = System.currentTimeMillis();
	long timeoutMillis = start + TimeUnit.MILLISECONDS.convert( timeout, unit ); 

	boolean innerTerminated = inner.awaitTermination( timeout, unit );
	if ( innerTerminated )
	{
	    long now = System.currentTimeMillis();
	    while (!isTerminated()) 
	    {
		if ( now > timeoutMillis ) return false;
		this.wait( timeoutMillis - now );
	    }
	    return true;
	}
	else
        {
	    return false;
	}
    }

    //MT: no need to synchronize
    public void execute( Runnable runnable )
    { inner.execute( newTaskFor( runnable, null ) ); }

    //MT: no need to synchronize
    protected  RunnableFuture newTaskFor(Callable callable) {
	PermitAcquiringCallable pac = new PermitAcquiringCallable( callable );
	ReleasingFutureTask     rft = new ReleasingFutureTask( pac );
	pac.setTask( rft );
	return rft;
    }

    //MT: no need to synchronize
    protected  RunnableFuture newTaskFor(Runnable runnable, V result) {
	PermitAcquiringRunnable par = new PermitAcquiringRunnable( runnable );
	ReleasingFutureTask     rft = new ReleasingFutureTask( par, result );
	par.setTask( rft );
	return rft;
    }

    // MT: Call only with this' lock
    private boolean shouldWait() 
    {
	switch ( state ) 
	{
	case SHUTDOWN:
	case SHUTDOWN_NOW:
	    return permits == blockBound; // permits will be zero in SHUTDOWN_NOW, and blockBound > 0, so we'll never wait()
	case ACCEPTING:
	    return false;
	case SATURATED:
	case UNWINDING:
	    return true;
	default:
	    throw new AssertionError("This should be dead code.");
	}
    }

    private synchronized void acquirePermit( Runnable task )
    {
	try 
	{
	    switch ( state ) 
	    {
	    case SHUTDOWN:
	    case SHUTDOWN_NOW:
		throw new RejectedExecutionException( this + " has been shut down. [state=" + state + "]" );
	    case ACCEPTING:
	    case SATURATED:
	    case UNWINDING:
		while ( shouldWait() ) 
		{
		    try 
		    {
			waiters.put( Thread.currentThread(), task );
			this.wait();
		    }
		    finally
		    { waiters.remove( Thread.currentThread() ); }
		}
	    
		if ( state != SHUTDOWN_NOW )
		{
		    ++permits;
		    if ( permits == blockBound ) updateState( SATURATED );
		}
	    }
	}
	catch ( InterruptedException e )
	{
	    throw new RejectedExecutionException( this + " has been forcibly shut down. [state=" + state + "]", e );
	}
    }

    private synchronized void releasePermit()
    {
	--permits;

	if ( permits < restartBeneath )
	{
	    updateState( ACCEPTING );
	}
	else if ( state == SATURATED && permits < blockBound )
	{
	    updateState( UNWINDING );
	}
    }

    // MT: call only from methods holding this' lock
    private void updateState( State newState )
    {
	switch ( this.state )
	{
	case ACCEPTING:
	case SATURATED:
	case UNWINDING:
	    if ( this.state != newState ) doUpdateState( newState );
	    break;
	case SHUTDOWN:
	    if ( newState == SHUTDOWN_NOW ) doUpdateState( newState );
	    break;
	case SHUTDOWN_NOW:
	    // can't change states from SHUTDOWN_NOW
	}
    }

    // MT: call only from methods holding this' lock
    private void doUpdateState( State newState )
    {
	if (logger.isLoggable( MLevel.FINE ))
	    logger.log(MLevel.FINE, "State transition " + this.state + " => " + newState + "; blockBound=" + blockBound + "; restartBeneath=" + restartBeneath + "; permits=" + permits );
	
	this.state = newState;
	if ( this.state == SHUTDOWN_NOW ) this.permits = 0;
	this.notifyAll();

    }

    private final class PermitAcquiringCallable implements Callable, DelayedTaskSettable
    {
	Callable            callable;
	ReleasingFutureTask task;
	    
	PermitAcquiringCallable( Callable callable )
	{
	    this.callable = callable;
	}

	public void setTask( ReleasingFutureTask task )
	{
	    this.task = task;
	}
	    
	public V call() throws Exception
	{
	    acquirePermit( this.task );
	    return callable.call();
	}
    }
    
    private final class PermitAcquiringRunnable implements Runnable, DelayedTaskSettable
    {
	Runnable               runnable;
	ReleasingFutureTask task;
	
	PermitAcquiringRunnable( Runnable runnable )
	{
	    this.runnable = runnable;
	}
	
	public void setTask( ReleasingFutureTask task )
	{
	    this.task = task;
	}

	public void run()
	{
	    acquirePermit( this.task );
	    runnable.run();
	}
    }

    private interface DelayedTaskSettable
    {
	public void setTask( ReleasingFutureTask task );
    }

    private final class ReleasingFutureTask extends FutureTask
    {
	
	ReleasingFutureTask(PermitAcquiringCallable callable)
	{
	    super( callable );
	}

	ReleasingFutureTask(PermitAcquiringRunnable runnable, V result)
	{ super( runnable, result ); }

	protected void done() 
	{ releasePermit(); }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy