之前写了一篇文章关于四种线程池的解析。
但是对于FixedThreadPool与CachedThreadPool适用的场景其实还是比较模糊难以界定的。所以笔者今天通过设计大任务并发和小任务并发来验证FixedThreadPool与CachedThreadPool的适用场景。
首先我设计了一个任务基类,它通过计算圆周率来模拟cpu的密集计算、通过写日志到本地文件来模拟IO。
这两个方法都通过参数n来调整任务的大小规模。
public class Task { /** * 通过计算圆周率模拟cpu计算 * 通过公式 π=4*(1-1/3+1/5-1/7+1/9-1/11+....) * * @return */ public static double calculatePI(long n) { double item = 0.0; double sum = 0; int flag = -1; for (int i = 0; i <= n; i++) { flag *= -1; item = flag * 1.0 / (2 * i + 1); sum += item; } return sum * 4; } /** * 通过写日志模拟IO操作 * @param n */ public static void writeIO(int n) { try { Date date = new Date(); SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS"); String fileName = Thread.currentThread().getName() + "-" + format.format(date) + ".log"; FileOutputStream os = new FileOutputStream("C://Users//valarchie//Desktop//logs//" + fileName); for (int i = 0; i < n; i++) { os.write(("写入日志" + i + "次").getBytes()); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
在笔者的设计当中大任务的规模是小任务的10倍,具体请看代码:
大任务
public class BigTask extends Task implements Runnable { private CountDownLatch latch; public BigTask(CountDownLatch latch) { this.latch = latch; } public static double calculatePI() { return calculatePI(100000000); } public static void writeIO() { writeIO(100); } @Override public void run() { calculatePI(); writeIO(); latch.countDown(); } }
小任务:
public class SmallTask extends Task implements Runnable { private CountDownLatch latch; public SmallTask(CountDownLatch latch) { this.latch = latch; } public static double calculatePI() { return calculatePI(10000000); } public static void writeIO() { writeIO(10); } @Override public void run() { calculatePI(); writeIO(); latch.countDown(); } }
通过测试我们得出一个小任务的运行时间大概在86ms左右。一个大任务的运行时间大概在575ms左右。
接下来我们分别测试100个大任务和100个小任务分别在单线程、FixedThreadPool、CachedThreadPool三种情况下的运行时间(我的笔记本是4核的,经过简单测试FixedThreadPool在16线程数的情况下性能最优良)。
我们使用CountDownLatch的计算多线程的运行时间,以下是多线程的测试代码模板:
public static void main(String[] args) { int taskCount = 100; CountDownLatch latch = new CountDownLatch(taskCount); ExecutorService executorService = Executors.newFixedThreadPool(16); long t1 = System.currentTimeMillis(); for (int i = 0; i < taskCount; i++) { executorService.submit(new SmallTask(latch)); } try { latch.await(); } catch (InterruptedException e) { e.printStackTrace(); } long t2 = System.currentTimeMillis(); // 得出多线程的运行时间 System.out.println(t2 - t1); executorService.shutdown(); }
任务模型*100 | 单线程 | FixedThreadPool | CachedThreadPool |
---|---|---|---|
大任务 | 45067ms | 6613ms | 6224ms |
小任务 | 4754ms | 722ms | 726ms |
通过统计发现多线程的性能比单线程的性能优异很多,但是其实FixedThreadPool和CachedThreadPool的性能差异是差不多相等的并没有比较大差别。
为了更严谨一点,我们控制任务方法的规模和任务数量的规模再进行一次测试
任务模型*100 | FixedThreadPool | CachedThreadPool |
---|---|---|
大任务方法规模*10 | 78738ms | 79669ms |
大任务数量规模*10 | 73654ms | 69343ms |
笔者经过验证得出的结论是两种线程池其实在性能上没有非常大差别,但是FixedThreadPool可以控制线程的并发数量,而CachedThreadPool不能控制线程的并发数量。如果线程数量爆发增长的话对系统会带来危害。个人认为使用FixedThreadPool会更好。