Java ExecutorService 重要BUG两例

声明
本文为Gleasy原创文章,转载请指明引自Gleasy团队博客

BUG1
请看下面的代码:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestExecutor {
	 public static void main(String[] args) throws InterruptedException {
 while(true) {
 	new TestThread().run();
 }
	}
	 
	public static class TestThread { 
		ExecutorService es = Executors.newFixedThreadPool(3);

		public void run() { 
			Future f = es.submit(new Runnable(){
				@Override
				public void run() {
					System.out.println("Name:" Thread.currentThread().getName() 
				 " doing something"); 
				}
			}); 
 try {
				f.get();
			} catch (Exception e) {
				e.printStackTrace();
			}
		} 
	} 
}

 

上面这段代码在JDK1.6,JRCOKIT28.1.x 以下版本运行,会得到以下结果:

[WARN ] Thread table can't grow past 16383 threads.

[ERROR][thread ] Could not start thread pool-16110-thread-1. errorcode -1
Exception in thread "Main Thread" java.lang.Error: errorcode -1
	at java.lang.Thread.start0(Native Method)
	at java.lang.Thread.start(Thread.java:640)
	at java.util.concurrent.ThreadPoolExecutor.addIfUnderCorePoolSize(ThreadPoolExecutor.java:703)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:652)
	at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:78)
	at com.gleasy.library.cloud.dfs.test.TestExecutor$TestThread.run(TestExecutor.java:18)
	at com.gleasy.library.cloud.dfs.test.TestExecutor.main(TestExecutor.java:10)

 

这个结果很怪异,因为TestThread对象不断创建和销毁,ExecutorService对象是TestThread对象的内部属性。当TestThread对象用完之后,es也应该被释放,而不应该对后面其它调用产生影响。但实际上,创建TestThread对象超过16000多次之后,再创建的时候就会报上面的错。这说明,通过 Executors.newFixedThreadPool(3)创建的线程池,会对后续的调用产生影响。这个BUG很阴险,如果稍不注意,在一个经常被创建的对象里面创建了线程池,那么整个JVM都将面临着不能再创建新的线程池的风险。而在研发过程中,这个BUG不会出错,而且是测不出来的。严重性在于它会影响整个JVM所有线程池的创建,令到整个JVM的其它应用都无法使用。

再过一个实验:

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestExecutor {
	 public static void main(String[] args) throws InterruptedException {
		 int i=0;
 while(true) {
 	new TestThread().run();
 	i ;
 	System.out.println("i:" i);
 }
	}
	 
	public static class TestThread { 
		ExecutorService es = Executors.newFixedThreadPool(100);

		public void run() { 
			final CountDownLatch c = new CountDownLatch(100);
			for(int i=0;i<100;i ){
				Future f = es.submit(new Runnable(){
					@Override
					public void run() {
						try {
							Thread.sleep(20);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						c.countDown();
					}
				}); 
			}
			try {
				c.await();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
 //es.shutdown();
		} 
	} 
}

 

结果如下:

i:150
i:151
i:152
i:153
i:154
i:155
i:156
i:157
i:158
i:159
i:160
i:161
[WARN ] Thread table can't grow past 16383 threads.

[ERROR][thread ] Could not start thread pool-162-thread-10. errorcode -1
Exception in thread "Main Thread" java.lang.Error: errorcode -1

 

在上面这个实验中,通过不断创建TestThread对象,在对象里面创建线程池,线程数为100,然后等这些线程全部执行完毕,才开始创建下一下。
从结果看出,在创建第161个线程池的时候,就开始报这个错了。说明这个错误的本质是线程数量,而非池的数量。由始至终,线程数量没有减少,一直在增加,一直增至16382,开始抛上面的异常了。导致这个异常的本质是线程池不会自动关闭!

解决方法是调用ExecutorService的shutdown或者shutdownNow方法将创建的线程池显式关闭。

package com.gleasy.library.cloud.dfs.test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class TestExecutor {
	 public static void main(String[] args) throws InterruptedException {
 while(true) {
 	new TestThread().run();
 }
	}
	 
	public static class TestThread { 
		ExecutorService es = Executors.newFixedThreadPool(3);

		public void run() { 
			Future f = es.submit(new Runnable(){
				@Override
				public void run() {
					System.out.println("Name:" Thread.currentThread().getName() 
				 " doing something"); 
				}
			}); 
 try {
				f.get();
			} catch (Exception e) {
				e.printStackTrace();
			}
 es.shutdown();
		} 
	} 
}

 

这个解决方法很不完善,因为很多时候,我们都不知道我们要做的事情是否已经做完,什么时候调用shutdown方法合适呢?最理想是在对象被析构时调用,但JAVA没有析构函数,所以,坑爹啊!!不完美的解决方法是,cachedThreadPool全局用同一个,fixsize的threadpool,用完要自己shutdown;或者说不确定什么时候shutdown的就全局大家共用,能确定用完的就自己shutdown.

BUG2
看下面的代码:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

public class TestThreadPool {
	private static final ExecutorService pool = Executors.newCachedThreadPool();

	public static void main(String[] args) online casino  throws InterruptedException {
		for (int i = 0; i < 10; i ) {
			pool.execute(new MyTask(i));
		}

		for (int i = 0; i < 40; i ) {
			System.out.println("poolSize:" ((ThreadPoolExecutor) pool).getPoolSize());
			Thread.sleep(1000L);
		}
		pool.shutdown();
	}

	static class MyTask implements Runnable {
		private int num;

		public MyTask(int index) {
			this.num = index;
		}

		public void run() {
			try {
				Thread.sleep(5000L);
				System.out.println("task " num " executed by " Thread.currentThread());
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			if (num < 5) {
				throw new RuntimeException("task " num ",executed by " Thread.currentThread());
			}
		}
	}

}

 

运行结果如下:

poolSize:5
poolSize:5
poolSize:5
poolSize:5
poolSize:5
task 0 executed by Thread[pool-1-thread-1,5,main]
task 1 executed by Thread[pool-1-thread-2,5,main]
task 3 executed by Thread[pool-1-thread-4,5,main]
task 4 executed by Thread[pool-1-thread-5,5,main]
Exception in thread "pool-1-thread-1" task 2 executed by Thread[pool-1-thread-3,5,main]
poolSize:1
Exception in thread "pool-1-thread-4" Exception in thread "pool-1-thread-5" Exception in thread "pool-1-thread-3" java.lang.RuntimeException: task 0,executed by Thread[pool-1-thread-1,5,main]
	at com.gleasy.library.cloud.dfs.test.TestThreadPool$MyTask.run(TestThreadPool.java:37)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:909)
	at java.lang.Thread.run(Thread.java:662)
Exception in thread "pool-1-thread-2" java.lang.RuntimeException: task 1,executed by Thread[pool-1-thread-2,5,main]
	at com.gleasy.library.cloud.dfs.test.TestThreadPool$MyTask.run(TestThreadPool.java:37)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:909)
	at java.lang.Thread.run(Thread.java:662)
java.lang.RuntimeException: task 4,executed by Thread[pool-1-thread-5,5,main]
	at com.gleasy.library.cloud.dfs.test.TestThreadPool$MyTask.run(TestThreadPool.java:37)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:909)
	at java.lang.Thread.run(Thread.java:662)
java.lang.RuntimeException: task 3,executed by Thread[pool-1-thread-4,5,main]
	at com.gleasy.library.cloud.dfs.test.TestThreadPool$MyTask.run(TestThreadPool.java:37)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:909)
	at java.lang.Thread.run(Thread.java:662)
java.lang.RuntimeException: task 2,executed by Thread[pool-1-thread-3,5,main]
	at com.gleasy.library.cloud.dfs.test.TestThreadPool$MyTask.run(TestThreadPool.java:37)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:909)
	at java.lang.Thread.run(Thread.java:662)
poolSize:1
poolSize:1
poolSize:1
poolSize:1
task 5 executed by Thread[pool-1-thread-6,5,main]
poolSize:1
poolSize:1
poolSize:1
poolSize:1
poolSize:1
task 6 executed by Thread[pool-1-thread-6,5,main]
poolSize:1

 

从上面的结果看到,当使用execute提交任务的时候,其中4个任务抛了异常,导致后续任务全部都在1个线程里面执行了。等于是串行执行。这对程序的性能有极大的影响。本来是多线程执行,结果因为执行任务的时候其中某些任务抛异常,可用线程数就大量减少了。这个BUG同样极为隐晦。

解决方案如下,使用submit代替execute提交任务。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

public class TestThreadPool {
	private static final ExecutorService pool = Executors.newFixedThreadPool(5);

	public static void main(String[] args) throws InterruptedException {
		for (int i = 0; i < 10; i ) {
			pool.submit(new MyTask(i));
		}

		for (int i = 0; i < 40; i ) {
			System.out.println("poolSize:" ((ThreadPoolExecutor) pool).getPoolSize());
			Thread.sleep(1000L);
		}
		pool.shutdown();
	}

	static class MyTask implements Runnable {
		private int num;

		public MyTask(int index) {
			this.num = index;
		}

		public void run() {
			try {
				Thread.sleep(5000L);
				System.out.println("task " num " executed by " Thread.currentThread());
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			if (num < 5) {
				throw new RuntimeException("task " num ",executed by " Thread.currentThread());
			}
		}
	}

}

 

问题解决。

poolSize:5
poolSize:5
poolSize:5
poolSize:5
poolSize:5
task 0 executed by Thread[pool-1-thread-1,5,main]
task 3 executed by Thread[pool-1-thread-4,5,main]
task 2 executed by Thread[pool-1-thread-3,5,main]
task 4 executed by Thread[pool-1-thread-5,5,main]
task 1 executed by Thread[pool-1-thread-2,5,main]
poolSize:5
poolSize:5
poolSize:5
poolSize:5
poolSize:5
task 5 executed by Thread[pool-1-thread-1,5,main]
task 6 executed by Thread[pool-1-thread-2,5,main]
task 9 executed by Thread[pool-1-thread-4,5,main]
task 7 executed by Thread[pool-1-thread-3,5,main]
task 8 executed by Thread[pool-1-thread-5,5,main]
poolSize:5
poolSize:5
poolSize:5

 

此条目发表在 Java技术 分类目录。将固定链接加入收藏夹。

发表评论