[SPARK-3795] Heuristics for dynamically scaling executors

This is part of a bigger effort to provide elastic scaling of executors within a Spark application ([SPARK-3174](https://issues.apache.org/jira/browse/SPARK-3174)). This PR does not provide any functionality by itself; it is a skeleton that is missing a mechanism to be added later in [SPARK-3822](https://issues.apache.org/jira/browse/SPARK-3822).

Comments and feedback are most welcome. For those of you reviewing this in detail, I highly recommend doing it through your favorite IDE instead of through the diff here.
I would refactor things slightly to make this more testable. The main changes I would do is to use a pluggable clock rather than calling `System.currentTimeMillis`. The second thing I would do is move the logic here out into a function called `computeState`. Then I would use an executor service here to schedule invocations of that function.

http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ScheduledExecutorService.html

Also, it would be good to understand the behavior if an exception is thrown here. At present it seems like it will simply silently die. I would instead log an error with the exception and say that elastic scaling has failed.

Once you've mocked out the clock it will be easier to test things in a nice way. Right now you have `Thread.sleep` in the tests (evil) and it really limits the amount of testing we can do.