【线程池】Executors中的newSingleThreadExecutor和newFixedThreadPool(1)的区别

在上一篇【线程池】深入理解Executors类时,提到了newSingleThreadExecutor和newFixedThreadPool(1)的区别,查阅了大量资料,自己也做了一些实验,但是还是有很多不清楚的地方,这篇文章主要是用作讨论,如果有大佬有好的回答,拜托请多多指教。


大部分博客中都提到两点:1、Single方法可以保证线程执行顺序,采用FIFO,先提交的任务先执行,而Fixed(1)不保证。

2、在Single方法中,当线程执行出现异常时,它会重新创建一个线程替换之前的线程继续执行,而Fixed(1)不行。


针对上面两点,我做了实验,结果如下:

1、执行顺序

工具类:

public class ThreadPoolUtil implements Runnable{
	
	private Integer index;
	
	public ThreadPoolUtil(Integer index) {
		this.index = index;
	}
	
	@Override
	public void run() {
		try {
			System.out.println(index+"开始处理线程!");
			Thread.sleep(50);
			System.out.println("线程标识是:"+this.toString());
			//System.out.println(index+"处理结束!");
		}
		catch(InterruptedException e) {
			e.printStackTrace();
		}
	}
	
}

创建newSingleThreadExecutor线程池类:

ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
System.out.println("---------------newSingleThreadExecutor--------------");
for(int i = 0; i < 200; i++) {
	final int index = i;
	newSingleThreadExecutor.execute(new ThreadPoolUtil(index));
}

结果:全部按顺序运行。

创建newFixedThreadPool线程池类:

ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(1);
System.out.println("------------newFixedThreadPool-------------");
for(int i = 0; i < 1000; i++) {
	final int index = i;
	newFixedThreadPool.execute(new ThreadPoolUtil(index));
}

结果:也是按顺序执行。

不知道是不是我验证方法有误,1000以内都是按照顺序执行的,和newSingleThreadExecutor没有区别。


2、线程执行出现异常

创建newSingleThreadExecutor线程池类:

ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();
System.out.println("---------------newSingleThreadExecutor--------------");
for(int i = 0; i < 100; i++) {
	final int index = i;
	newSingleThreadExecutor.execute(()->{
		if(index == 20) {
			throw new IllegalStateException("Error");
		}
		System.out.println(Thread.currentThread() + ".."+index);
	});
}

结果:

Thread[pool-1-thread-1,5,main]..19
Exception in thread "pool-1-thread-1" Thread[pool-1-thread-2,5,main]..21
Thread[pool-1-thread-2,5,main]..22
...
...
Thread[pool-1-thread-2,5,main]..32
Thread[pool-1-thread-2,5,main]..33java.lang.IllegalStateException: Error


Thread[pool-1-thread-2,5,main]..34
Thread[pool-1-thread-2,5,main]..35	at threadTest.ThreadPools.lambda$0(ThreadPools.java:57)


Thread[pool-1-thread-2,5,main]..36
Thread[pool-1-thread-2,5,main]..37	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)


	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
Thread[pool-1-thread-2,5,main]..38
...
...
Thread[pool-1-thread-2,5,main]..44
Thread[pool-1-thread-2,5,main]..45
	at java.lang.Thread.run(Thread.java:748)Thread[pool-1-thread-2,5,main]..46
Thread[pool-1-thread-2,5,main]..47


Thread[pool-1-thread-2,5,main]..48


创建newFixedThreadPool线程池类:

ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(1);
System.out.println("------------newFixedThreadPool-------------");
for(int i = 0; i < 100; i++) {
	final int index = i;	
	newFixedThreadPool.execute(()->{
		if(index == 20) {
			throw new IllegalStateException("Error");
		}
		System.out.println(Thread.currentThread() + ".."+index);
	});
}

结果:

Thread[pool-1-thread-1,5,main]..18
Thread[pool-1-thread-1,5,main]..19
Exception in thread "pool-1-thread-1" Thread[pool-1-thread-2,5,main]..21
Thread[pool-1-thread-2,5,main]..22
Thread[pool-1-thread-2,5,main]..23
...
...
Thread[pool-1-thread-2,5,main]..42
java.lang.IllegalStateException: ErrorThread[pool-1-thread-2,5,main]..43
Thread[pool-1-thread-2,5,main]..44


Thread[pool-1-thread-2,5,main]..45
Thread[pool-1-thread-2,5,main]..46
Thread[pool-1-thread-2,5,main]..47
Thread[pool-1-thread-2,5,main]..48
	at threadTest.ThreadPools.lambda$0(ThreadPools.java:27)Thread[pool-1-thread-2,5,main]..49


Thread[pool-1-thread-2,5,main]..50
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)Thread[pool-1-thread-2,5,main]..51


Thread[pool-1-thread-2,5,main]..52
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)Thread[pool-1-thread-2,5,main]..53


Thread[pool-1-thread-2,5,main]..54
	at java.lang.Thread.run(Thread.java:748)Thread[pool-1-thread-2,5,main]..55
Thread[pool-1-thread-2,5,main]..56

可以看见,在第20个线程时抛出异常,当前线程的标志改变了,说明新建了线程继续执行。

所以当线程因为异常终止时,newFixedThreadPool(1)可以新建线程继续执行。


做完上面两个实验,我们发现newSingleThreadExecutor和newFixedThreadPool(1)好像没有任何区别,那到底是不是这样呢?我们还是要从源码讲起。

源码分析

newFixedThreadPool源码

/**
     * Creates a thread pool that reuses a fixed number of threads
     * operating off a shared unbounded queue.  At any point, at most
     * {@code nThreads} threads will be active processing tasks.
     * If additional tasks are submitted when all threads are active,
     * they will wait in the queue until a thread is available.
     * If any thread terminates due to a failure during execution
     * prior to shutdown, a new one will take its place if needed to
     * execute subsequent tasks.  The threads in the pool will exist
     * until it is explicitly {@link ExecutorService#shutdown shutdown}.
     *
     * @param nThreads the number of threads in the pool
     * @return the newly created thread pool
     * @throws IllegalArgumentException if {@code nThreads <= 0}
     */
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

其中nThread为线程数量,程序员可自行设置。注释中有句话:如果任何线程在关闭之前的运行期间因为失败停止了,如果需要的话,一个新的线程将代替它继续执行后续的任务

newSingleThreadExecutor源码

/**
     * Creates an Executor that uses a single worker thread operating
     * off an unbounded queue. (Note however that if this single
     * thread terminates due to a failure during execution prior to
     * shutdown, a new one will take its place if needed to execute
     * subsequent tasks.)  Tasks are guaranteed to execute
     * sequentially, and no more than one task will be active at any
     * given time. Unlike the otherwise equivalent
     * {@code newFixedThreadPool(1)} the returned executor is
     * guaranteed not to be reconfigurable to use additional threads.
     *
     * @return the newly created single-threaded Executor
     */
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

注释中有一段话,大致意思是和其他等效的线程池(比如newFixedThreadPool(1))不同,它保证不能重新配置从而使用其他线程。比如以下代码:

 // final ExecutorService single = Executors.newSingleThreadExecutor();
       final ExecutorService fixed = Executors.newFixedThreadPool(1);
        ThreadPoolExecutor executor = (ThreadPoolExecutor) fixed;
        executor.setCorePoolSize(4);
如果新建的线程池类是Single,那么会报错,
Exception in thread "main" java.lang.ClassCastException: java.util.concurrent.Executors$FinalizableDelegatedExecutorService cannot be cast to java.util.concurrent.ThreadPoolExecutor
	at threadTest.ThreadPools.main(ThreadPools.java:37)

而Fixed可以正常运行。

源码注释中还有一句话,如果这个单线程在关闭之前的运行期间因为失败停止了,如果需要的话,一个新的线程将代替它继续执行后续的任务。可以看出来这两个方法在意外停止后都会有新的线程继续执行任务,所以文章开头提到了第二点是不成立的。

我们可以发现newSingleThreadExecutor其实就是newFixedThreadPool(1)用FinalizableDelegatedExecutorService()包裹起来的产物。那么我们来看看这个类的源码

static class FinalizableDelegatedExecutorService
        extends DelegatedExecutorService {
        FinalizableDelegatedExecutorService(ExecutorService executor) {
            super(executor);
        }
        
        //GC的时候停掉线程池
        protected void finalize() {
            super.shutdown();
        }
    }

这个类内部除了构造方法之外只有一个GC时停掉线程池的方法,我们都知道Java中的内存管理一般都是JVM来完成的,程序员不需要做什么事情,所以这个方法我们可以不看。这个类继承了DelegatedExecutorService,我们来看看这个类的源码

 /**
     * A wrapper class that exposes only the ExecutorService methods
     * of an ExecutorService implementation.
     */
    static class DelegatedExecutorService extends AbstractExecutorService {
        private final ExecutorService e;
        DelegatedExecutorService(ExecutorService executor) { e = executor; }
        public void execute(Runnable command) { e.execute(command); }
        public void shutdown() { e.shutdown(); }
        public List<Runnable> shutdownNow() { return e.shutdownNow(); }
        public boolean isShutdown() { return e.isShutdown(); }
        public boolean isTerminated() { return e.isTerminated(); }
        public boolean awaitTermination(long timeout, TimeUnit unit)
            throws InterruptedException {
            return e.awaitTermination(timeout, unit);
        }
        public Future<?> submit(Runnable task) {
            return e.submit(task);
        }
        public <T> Future<T> submit(Callable<T> task) {
            return e.submit(task);
        }
        public <T> Future<T> submit(Runnable task, T result) {
            return e.submit(task, result);
        }
        public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
            throws InterruptedException {
            return e.invokeAll(tasks);
        }
        public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                             long timeout, TimeUnit unit)
            throws InterruptedException {
            return e.invokeAll(tasks, timeout, unit);
        }
        public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
            throws InterruptedException, ExecutionException {
            return e.invokeAny(tasks);
        }
        public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                               long timeout, TimeUnit unit)
            throws InterruptedException, ExecutionException, TimeoutException {
            return e.invokeAny(tasks, timeout, unit);
        }
    }

这个类有一个注释,大致是说这是一个包装类,只公开ExecutorService接口的ExecutorService方法。你不能自行配置。而这个类也仅仅是调用了传入的AbstractExecutorService类的方法,并没有增加新的方法。


综上所述我们可以得出以下结论:

在小量线程的运行中,newSingleThreadExecutor和newFixedThreadPool(1)都可以保证线程的顺序执行。而从代码上看newSingleThreadExecutor拒绝程序员重新配置加入额外的线程,可以确保线程池中只有一个线程。除此之外,newSingleThreadExecutor和newFixedThreadPool(1)没有任何区别。

写到这里,我有一个疑问:newFixedThreadPool(1)到底能不能保证线程的顺序执行?

如果不能,newSingleThreadExecutor的实现代码几乎和newFixedThreadPool(1)完全一样,那么newSingleThreadExecutor到底是怎么保证线程一定顺序执行的?

如果能,那么是否有必要同时存在如此相似的两个方法?能不能从真正的应用或者其他方面进行解释?

如果有高人指点,感激不尽。

参考资料:https://segmentfault.com/q/1010000011185322


猜你喜欢

转载自blog.csdn.net/zxm490484080/article/details/80949115