• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Written by Doug Lea with assistance from members of JCP JSR-166
3  * Expert Group and released to the public domain, as explained at
4  * http://creativecommons.org/publicdomain/zero/1.0/
5  */
6 
7 package java.util.concurrent;
8 
9 import java.util.*;
10 import java.util.concurrent.atomic.AtomicInteger;
11 import java.security.AccessControlContext;
12 import java.security.AccessController;
13 import java.security.PrivilegedAction;
14 import java.security.PrivilegedExceptionAction;
15 import java.security.PrivilegedActionException;
16 
17 // BEGIN android-note
18 // removed security manager docs
19 // END android-note
20 /**
21  * Factory and utility methods for {@link Executor}, {@link
22  * ExecutorService}, {@link ScheduledExecutorService}, {@link
23  * ThreadFactory}, and {@link Callable} classes defined in this
24  * package. This class supports the following kinds of methods:
25  *
26  * <ul>
27  *   <li> Methods that create and return an {@link ExecutorService}
28  *        set up with commonly useful configuration settings.
29  *   <li> Methods that create and return a {@link ScheduledExecutorService}
30  *        set up with commonly useful configuration settings.
31  *   <li> Methods that create and return a "wrapped" ExecutorService, that
32  *        disables reconfiguration by making implementation-specific methods
33  *        inaccessible.
34  *   <li> Methods that create and return a {@link ThreadFactory}
35  *        that sets newly created threads to a known state.
36  *   <li> Methods that create and return a {@link Callable}
37  *        out of other closure-like forms, so they can be used
38  *        in execution methods requiring {@code Callable}.
39  * </ul>
40  *
41  * @since 1.5
42  * @author Doug Lea
43  */
44 public class Executors {
45 
46     /**
47      * Creates a thread pool that reuses a fixed number of threads
48      * operating off a shared unbounded queue.  At any point, at most
49      * {@code nThreads} threads will be active processing tasks.
50      * If additional tasks are submitted when all threads are active,
51      * they will wait in the queue until a thread is available.
52      * If any thread terminates due to a failure during execution
53      * prior to shutdown, a new one will take its place if needed to
54      * execute subsequent tasks.  The threads in the pool will exist
55      * until it is explicitly {@link ExecutorService#shutdown shutdown}.
56      *
57      * @param nThreads the number of threads in the pool
58      * @return the newly created thread pool
59      * @throws IllegalArgumentException if {@code nThreads <= 0}
60      */
newFixedThreadPool(int nThreads)61     public static ExecutorService newFixedThreadPool(int nThreads) {
62         return new ThreadPoolExecutor(nThreads, nThreads,
63                                       0L, TimeUnit.MILLISECONDS,
64                                       new LinkedBlockingQueue<Runnable>());
65     }
66 
67     /**
68      * Creates a thread pool that maintains enough threads to support
69      * the given parallelism level, and may use multiple queues to
70      * reduce contention. The parallelism level corresponds to the
71      * maximum number of threads actively engaged in, or available to
72      * engage in, task processing. The actual number of threads may
73      * grow and shrink dynamically. A work-stealing pool makes no
74      * guarantees about the order in which submitted tasks are
75      * executed.
76      *
77      * @param parallelism the targeted parallelism level
78      * @return the newly created thread pool
79      * @throws IllegalArgumentException if {@code parallelism <= 0}
80      * @since 1.8
81      * @hide
82      */
newWorkStealingPool(int parallelism)83     public static ExecutorService newWorkStealingPool(int parallelism) {
84         return new ForkJoinPool
85             (parallelism,
86              ForkJoinPool.defaultForkJoinWorkerThreadFactory,
87              null, true);
88     }
89 
90     /**
91      * Creates a work-stealing thread pool using all
92      * {@link Runtime#availableProcessors available processors}
93      * as its target parallelism level.
94      * @return the newly created thread pool
95      * @since 1.8
96      * @hide
97      */
newWorkStealingPool()98     public static ExecutorService newWorkStealingPool() {
99         return new ForkJoinPool
100             (Runtime.getRuntime().availableProcessors(),
101              ForkJoinPool.defaultForkJoinWorkerThreadFactory,
102              null, true);
103     }
104 
105     /**
106      * Creates a thread pool that reuses a fixed number of threads
107      * operating off a shared unbounded queue, using the provided
108      * ThreadFactory to create new threads when needed.  At any point,
109      * at most {@code nThreads} threads will be active processing
110      * tasks.  If additional tasks are submitted when all threads are
111      * active, they will wait in the queue until a thread is
112      * available.  If any thread terminates due to a failure during
113      * execution prior to shutdown, a new one will take its place if
114      * needed to execute subsequent tasks.  The threads in the pool will
115      * exist until it is explicitly {@link ExecutorService#shutdown
116      * shutdown}.
117      *
118      * @param nThreads the number of threads in the pool
119      * @param threadFactory the factory to use when creating new threads
120      * @return the newly created thread pool
121      * @throws NullPointerException if threadFactory is null
122      * @throws IllegalArgumentException if {@code nThreads <= 0}
123      */
newFixedThreadPool(int nThreads, ThreadFactory threadFactory)124     public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
125         return new ThreadPoolExecutor(nThreads, nThreads,
126                                       0L, TimeUnit.MILLISECONDS,
127                                       new LinkedBlockingQueue<Runnable>(),
128                                       threadFactory);
129     }
130 
131     /**
132      * Creates an Executor that uses a single worker thread operating
133      * off an unbounded queue. (Note however that if this single
134      * thread terminates due to a failure during execution prior to
135      * shutdown, a new one will take its place if needed to execute
136      * subsequent tasks.)  Tasks are guaranteed to execute
137      * sequentially, and no more than one task will be active at any
138      * given time. Unlike the otherwise equivalent
139      * {@code newFixedThreadPool(1)} the returned executor is
140      * guaranteed not to be reconfigurable to use additional threads.
141      *
142      * @return the newly created single-threaded Executor
143      */
newSingleThreadExecutor()144     public static ExecutorService newSingleThreadExecutor() {
145         return new FinalizableDelegatedExecutorService
146             (new ThreadPoolExecutor(1, 1,
147                                     0L, TimeUnit.MILLISECONDS,
148                                     new LinkedBlockingQueue<Runnable>()));
149     }
150 
151     /**
152      * Creates an Executor that uses a single worker thread operating
153      * off an unbounded queue, and uses the provided ThreadFactory to
154      * create a new thread when needed. Unlike the otherwise
155      * equivalent {@code newFixedThreadPool(1, threadFactory)} the
156      * returned executor is guaranteed not to be reconfigurable to use
157      * additional threads.
158      *
159      * @param threadFactory the factory to use when creating new
160      * threads
161      *
162      * @return the newly created single-threaded Executor
163      * @throws NullPointerException if threadFactory is null
164      */
newSingleThreadExecutor(ThreadFactory threadFactory)165     public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
166         return new FinalizableDelegatedExecutorService
167             (new ThreadPoolExecutor(1, 1,
168                                     0L, TimeUnit.MILLISECONDS,
169                                     new LinkedBlockingQueue<Runnable>(),
170                                     threadFactory));
171     }
172 
173     /**
174      * Creates a thread pool that creates new threads as needed, but
175      * will reuse previously constructed threads when they are
176      * available.  These pools will typically improve the performance
177      * of programs that execute many short-lived asynchronous tasks.
178      * Calls to {@code execute} will reuse previously constructed
179      * threads if available. If no existing thread is available, a new
180      * thread will be created and added to the pool. Threads that have
181      * not been used for sixty seconds are terminated and removed from
182      * the cache. Thus, a pool that remains idle for long enough will
183      * not consume any resources. Note that pools with similar
184      * properties but different details (for example, timeout parameters)
185      * may be created using {@link ThreadPoolExecutor} constructors.
186      *
187      * @return the newly created thread pool
188      */
newCachedThreadPool()189     public static ExecutorService newCachedThreadPool() {
190         return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
191                                       60L, TimeUnit.SECONDS,
192                                       new SynchronousQueue<Runnable>());
193     }
194 
195     /**
196      * Creates a thread pool that creates new threads as needed, but
197      * will reuse previously constructed threads when they are
198      * available, and uses the provided
199      * ThreadFactory to create new threads when needed.
200      * @param threadFactory the factory to use when creating new threads
201      * @return the newly created thread pool
202      * @throws NullPointerException if threadFactory is null
203      */
newCachedThreadPool(ThreadFactory threadFactory)204     public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
205         return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
206                                       60L, TimeUnit.SECONDS,
207                                       new SynchronousQueue<Runnable>(),
208                                       threadFactory);
209     }
210 
211     /**
212      * Creates a single-threaded executor that can schedule commands
213      * to run after a given delay, or to execute periodically.
214      * (Note however that if this single
215      * thread terminates due to a failure during execution prior to
216      * shutdown, a new one will take its place if needed to execute
217      * subsequent tasks.)  Tasks are guaranteed to execute
218      * sequentially, and no more than one task will be active at any
219      * given time. Unlike the otherwise equivalent
220      * {@code newScheduledThreadPool(1)} the returned executor is
221      * guaranteed not to be reconfigurable to use additional threads.
222      * @return the newly created scheduled executor
223      */
newSingleThreadScheduledExecutor()224     public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
225         return new DelegatedScheduledExecutorService
226             (new ScheduledThreadPoolExecutor(1));
227     }
228 
229     /**
230      * Creates a single-threaded executor that can schedule commands
231      * to run after a given delay, or to execute periodically.  (Note
232      * however that if this single thread terminates due to a failure
233      * during execution prior to shutdown, a new one will take its
234      * place if needed to execute subsequent tasks.)  Tasks are
235      * guaranteed to execute sequentially, and no more than one task
236      * will be active at any given time. Unlike the otherwise
237      * equivalent {@code newScheduledThreadPool(1, threadFactory)}
238      * the returned executor is guaranteed not to be reconfigurable to
239      * use additional threads.
240      * @param threadFactory the factory to use when creating new
241      * threads
242      * @return a newly created scheduled executor
243      * @throws NullPointerException if threadFactory is null
244      */
newSingleThreadScheduledExecutor(ThreadFactory threadFactory)245     public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
246         return new DelegatedScheduledExecutorService
247             (new ScheduledThreadPoolExecutor(1, threadFactory));
248     }
249 
250     /**
251      * Creates a thread pool that can schedule commands to run after a
252      * given delay, or to execute periodically.
253      * @param corePoolSize the number of threads to keep in the pool,
254      * even if they are idle
255      * @return a newly created scheduled thread pool
256      * @throws IllegalArgumentException if {@code corePoolSize < 0}
257      */
newScheduledThreadPool(int corePoolSize)258     public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
259         return new ScheduledThreadPoolExecutor(corePoolSize);
260     }
261 
262     /**
263      * Creates a thread pool that can schedule commands to run after a
264      * given delay, or to execute periodically.
265      * @param corePoolSize the number of threads to keep in the pool,
266      * even if they are idle
267      * @param threadFactory the factory to use when the executor
268      * creates a new thread
269      * @return a newly created scheduled thread pool
270      * @throws IllegalArgumentException if {@code corePoolSize < 0}
271      * @throws NullPointerException if threadFactory is null
272      */
newScheduledThreadPool( int corePoolSize, ThreadFactory threadFactory)273     public static ScheduledExecutorService newScheduledThreadPool(
274             int corePoolSize, ThreadFactory threadFactory) {
275         return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
276     }
277 
278     /**
279      * Returns an object that delegates all defined {@link
280      * ExecutorService} methods to the given executor, but not any
281      * other methods that might otherwise be accessible using
282      * casts. This provides a way to safely "freeze" configuration and
283      * disallow tuning of a given concrete implementation.
284      * @param executor the underlying implementation
285      * @return an {@code ExecutorService} instance
286      * @throws NullPointerException if executor null
287      */
unconfigurableExecutorService(ExecutorService executor)288     public static ExecutorService unconfigurableExecutorService(ExecutorService executor) {
289         if (executor == null)
290             throw new NullPointerException();
291         return new DelegatedExecutorService(executor);
292     }
293 
294     /**
295      * Returns an object that delegates all defined {@link
296      * ScheduledExecutorService} methods to the given executor, but
297      * not any other methods that might otherwise be accessible using
298      * casts. This provides a way to safely "freeze" configuration and
299      * disallow tuning of a given concrete implementation.
300      * @param executor the underlying implementation
301      * @return a {@code ScheduledExecutorService} instance
302      * @throws NullPointerException if executor null
303      */
unconfigurableScheduledExecutorService(ScheduledExecutorService executor)304     public static ScheduledExecutorService unconfigurableScheduledExecutorService(ScheduledExecutorService executor) {
305         if (executor == null)
306             throw new NullPointerException();
307         return new DelegatedScheduledExecutorService(executor);
308     }
309 
310     /**
311      * Returns a default thread factory used to create new threads.
312      * This factory creates all new threads used by an Executor in the
313      * same {@link ThreadGroup}. Each new
314      * thread is created as a non-daemon thread with priority set to
315      * the smaller of {@code Thread.NORM_PRIORITY} and the maximum
316      * priority permitted in the thread group.  New threads have names
317      * accessible via {@link Thread#getName} of
318      * <em>pool-N-thread-M</em>, where <em>N</em> is the sequence
319      * number of this factory, and <em>M</em> is the sequence number
320      * of the thread created by this factory.
321      * @return a thread factory
322      */
defaultThreadFactory()323     public static ThreadFactory defaultThreadFactory() {
324         return new DefaultThreadFactory();
325     }
326 
327     /**
328      * Legacy security code; do not use.
329      */
privilegedThreadFactory()330     public static ThreadFactory privilegedThreadFactory() {
331         return new PrivilegedThreadFactory();
332     }
333 
334     /**
335      * Returns a {@link Callable} object that, when
336      * called, runs the given task and returns the given result.  This
337      * can be useful when applying methods requiring a
338      * {@code Callable} to an otherwise resultless action.
339      * @param task the task to run
340      * @param result the result to return
341      * @return a callable object
342      * @throws NullPointerException if task null
343      */
callable(Runnable task, T result)344     public static <T> Callable<T> callable(Runnable task, T result) {
345         if (task == null)
346             throw new NullPointerException();
347         return new RunnableAdapter<T>(task, result);
348     }
349 
350     /**
351      * Returns a {@link Callable} object that, when
352      * called, runs the given task and returns {@code null}.
353      * @param task the task to run
354      * @return a callable object
355      * @throws NullPointerException if task null
356      */
callable(Runnable task)357     public static Callable<Object> callable(Runnable task) {
358         if (task == null)
359             throw new NullPointerException();
360         return new RunnableAdapter<Object>(task, null);
361     }
362 
363     /**
364      * Returns a {@link Callable} object that, when
365      * called, runs the given privileged action and returns its result.
366      * @param action the privileged action to run
367      * @return a callable object
368      * @throws NullPointerException if action null
369      */
callable(final PrivilegedAction<?> action)370     public static Callable<Object> callable(final PrivilegedAction<?> action) {
371         if (action == null)
372             throw new NullPointerException();
373         return new Callable<Object>() {
374             public Object call() { return action.run(); }};
375     }
376 
377     /**
378      * Returns a {@link Callable} object that, when
379      * called, runs the given privileged exception action and returns
380      * its result.
381      * @param action the privileged exception action to run
382      * @return a callable object
383      * @throws NullPointerException if action null
384      */
385     public static Callable<Object> callable(final PrivilegedExceptionAction<?> action) {
386         if (action == null)
387             throw new NullPointerException();
388         return new Callable<Object>() {
389             public Object call() throws Exception { return action.run(); }};
390     }
391 
392     /**
393      * Legacy security code; do not use.
394      */
395     public static <T> Callable<T> privilegedCallable(Callable<T> callable) {
396         if (callable == null)
397             throw new NullPointerException();
398         return new PrivilegedCallable<T>(callable);
399     }
400 
401     /**
402      * Legacy security code; do not use.
403      */
404     public static <T> Callable<T> privilegedCallableUsingCurrentClassLoader(Callable<T> callable) {
405         if (callable == null)
406             throw new NullPointerException();
407         return new PrivilegedCallableUsingCurrentClassLoader<T>(callable);
408     }
409 
410     // Non-public classes supporting the public methods
411 
412     /**
413      * A callable that runs given task and returns given result
414      */
415     static final class RunnableAdapter<T> implements Callable<T> {
416         final Runnable task;
417         final T result;
418         RunnableAdapter(Runnable task, T result) {
419             this.task = task;
420             this.result = result;
421         }
422         public T call() {
423             task.run();
424             return result;
425         }
426     }
427 
428     /**
429      * A callable that runs under established access control settings
430      */
431     static final class PrivilegedCallable<T> implements Callable<T> {
432         private final Callable<T> task;
433         private final AccessControlContext acc;
434 
435         PrivilegedCallable(Callable<T> task) {
436             this.task = task;
437             this.acc = AccessController.getContext();
438         }
439 
440         public T call() throws Exception {
441             try {
442                 return AccessController.doPrivileged(
443                     new PrivilegedExceptionAction<T>() {
444                         public T run() throws Exception {
445                             return task.call();
446                         }
447                     }, acc);
448             } catch (PrivilegedActionException e) {
449                 throw e.getException();
450             }
451         }
452     }
453 
454     /**
455      * A callable that runs under established access control settings and
456      * current ClassLoader
457      */
458     static final class PrivilegedCallableUsingCurrentClassLoader<T> implements Callable<T> {
459         private final Callable<T> task;
460         private final AccessControlContext acc;
461         private final ClassLoader ccl;
462 
463         PrivilegedCallableUsingCurrentClassLoader(Callable<T> task) {
464             // BEGIN android-removed
465             // SecurityManager sm = System.getSecurityManager();
466             // if (sm != null) {
467             //     // Calls to getContextClassLoader from this class
468             //     // never trigger a security check, but we check
469             //     // whether our callers have this permission anyways.
470             //     sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
471             //
472             //     // Whether setContextClassLoader turns out to be necessary
473             //     // or not, we fail fast if permission is not available.
474             //     sm.checkPermission(new RuntimePermission("setContextClassLoader"));
475             // }
476             // END android-removed
477             this.task = task;
478             this.acc = AccessController.getContext();
479             this.ccl = Thread.currentThread().getContextClassLoader();
480         }
481 
482         public T call() throws Exception {
483             try {
484                 return AccessController.doPrivileged(
485                     new PrivilegedExceptionAction<T>() {
486                         public T run() throws Exception {
487                             Thread t = Thread.currentThread();
488                             ClassLoader cl = t.getContextClassLoader();
489                             if (ccl == cl) {
490                                 return task.call();
491                             } else {
492                                 t.setContextClassLoader(ccl);
493                                 try {
494                                     return task.call();
495                                 } finally {
496                                     t.setContextClassLoader(cl);
497                                 }
498                             }
499                         }
500                     }, acc);
501             } catch (PrivilegedActionException e) {
502                 throw e.getException();
503             }
504         }
505     }
506 
507     /**
508      * The default thread factory
509      */
510     static class DefaultThreadFactory implements ThreadFactory {
511         private static final AtomicInteger poolNumber = new AtomicInteger(1);
512         private final ThreadGroup group;
513         private final AtomicInteger threadNumber = new AtomicInteger(1);
514         private final String namePrefix;
515 
516         DefaultThreadFactory() {
517             SecurityManager s = System.getSecurityManager();
518             group = (s != null) ? s.getThreadGroup() :
519                                   Thread.currentThread().getThreadGroup();
520             namePrefix = "pool-" +
521                           poolNumber.getAndIncrement() +
522                          "-thread-";
523         }
524 
525         public Thread newThread(Runnable r) {
526             Thread t = new Thread(group, r,
527                                   namePrefix + threadNumber.getAndIncrement(),
528                                   0);
529             if (t.isDaemon())
530                 t.setDaemon(false);
531             if (t.getPriority() != Thread.NORM_PRIORITY)
532                 t.setPriority(Thread.NORM_PRIORITY);
533             return t;
534         }
535     }
536 
537     /**
538      * Thread factory capturing access control context and class loader
539      */
540     static class PrivilegedThreadFactory extends DefaultThreadFactory {
541         private final AccessControlContext acc;
542         private final ClassLoader ccl;
543 
544         PrivilegedThreadFactory() {
545             super();
546             // BEGIN android-removed
547             // SecurityManager sm = System.getSecurityManager();
548             // if (sm != null) {
549             //     // Calls to getContextClassLoader from this class
550             //     // never trigger a security check, but we check
551             //     // whether our callers have this permission anyways.
552             //     sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
553             //
554             //     // Fail fast
555             //     sm.checkPermission(new RuntimePermission("setContextClassLoader"));
556             // }
557             // END android-removed
558             this.acc = AccessController.getContext();
559             this.ccl = Thread.currentThread().getContextClassLoader();
560         }
561 
562         public Thread newThread(final Runnable r) {
563             return super.newThread(new Runnable() {
564                 public void run() {
565                     AccessController.doPrivileged(new PrivilegedAction<Void>() {
566                         public Void run() {
567                             Thread.currentThread().setContextClassLoader(ccl);
568                             r.run();
569                             return null;
570                         }
571                     }, acc);
572                 }
573             });
574         }
575     }
576 
577     /**
578      * A wrapper class that exposes only the ExecutorService methods
579      * of an ExecutorService implementation.
580      */
581     static class DelegatedExecutorService extends AbstractExecutorService {
582         private final ExecutorService e;
583         DelegatedExecutorService(ExecutorService executor) { e = executor; }
584         public void execute(Runnable command) { e.execute(command); }
585         public void shutdown() { e.shutdown(); }
586         public List<Runnable> shutdownNow() { return e.shutdownNow(); }
587         public boolean isShutdown() { return e.isShutdown(); }
588         public boolean isTerminated() { return e.isTerminated(); }
589         public boolean awaitTermination(long timeout, TimeUnit unit)
590             throws InterruptedException {
591             return e.awaitTermination(timeout, unit);
592         }
593         public Future<?> submit(Runnable task) {
594             return e.submit(task);
595         }
596         public <T> Future<T> submit(Callable<T> task) {
597             return e.submit(task);
598         }
599         public <T> Future<T> submit(Runnable task, T result) {
600             return e.submit(task, result);
601         }
602         public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
603             throws InterruptedException {
604             return e.invokeAll(tasks);
605         }
606         public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
607                                              long timeout, TimeUnit unit)
608             throws InterruptedException {
609             return e.invokeAll(tasks, timeout, unit);
610         }
611         public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
612             throws InterruptedException, ExecutionException {
613             return e.invokeAny(tasks);
614         }
615         public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
616                                long timeout, TimeUnit unit)
617             throws InterruptedException, ExecutionException, TimeoutException {
618             return e.invokeAny(tasks, timeout, unit);
619         }
620     }
621 
622     static class FinalizableDelegatedExecutorService
623         extends DelegatedExecutorService {
624         FinalizableDelegatedExecutorService(ExecutorService executor) {
625             super(executor);
626         }
627         protected void finalize() {
628             super.shutdown();
629         }
630     }
631 
632     /**
633      * A wrapper class that exposes only the ScheduledExecutorService
634      * methods of a ScheduledExecutorService implementation.
635      */
636     static class DelegatedScheduledExecutorService
637             extends DelegatedExecutorService
638             implements ScheduledExecutorService {
639         private final ScheduledExecutorService e;
640         DelegatedScheduledExecutorService(ScheduledExecutorService executor) {
641             super(executor);
642             e = executor;
643         }
644         public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
645             return e.schedule(command, delay, unit);
646         }
647         public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
648             return e.schedule(callable, delay, unit);
649         }
650         public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
651             return e.scheduleAtFixedRate(command, initialDelay, period, unit);
652         }
653         public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
654             return e.scheduleWithFixedDelay(command, initialDelay, delay, unit);
655         }
656     }
657 
658     /** Cannot instantiate. */
659     private Executors() {}
660 }
661