在多线程程序的编写中,为了同步线程的执行状态,我们为了方便,经常会使用Thread.join()方法,须不知此方法有重大的性能缺陷,能将多线程程序变成单线程程序,执行时间瞬间翻倍,示例如下:
/** * 用于长时间的任务计算,一般求fabic(40)就会花费1秒的时间 * 花费时间呈指数增长速度 */static long fabic(int n) { if(n < 0) { throw new NumberFormatException("不能小于0"); } if(n == 1 || n == 2) { return 1; } return fabic(n - 1) + fabic(n - 2);}public static void main(String[] args) throws InterruptedException { long startTime = System.currentTimeMillis(); int NUM = 3; for(int i = 0; i < NUM; i ++) { // 创建线程 Thread th = new Thread(() -> { fabic(35 + i); }); th.start(); th.join(); } // 打印花费的时间 System.out.println(System.currentTimeMillis() - startTime);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
通过监测应用程序的执行时间,或者在线程内添加调试日志,我们发现,现在三个线程会依次执行,即线程0执行完了才执行线程1,类似于连续调用了3次th.run()方法(其实效率更低,因为还有线程创建、切换、销毁的时间)。
在上述的代码测试中,我们还发现了一个令人惊讶的现象,调用fabic的递归函数,JAVA的执行时间竟然超过Python 100多倍,下面是成绩对比:
斐波那契数N | JAVA(ms) | Python(ms) | 结果 |
---|---|---|---|
35 | 31 | 2991 | 9227465 |
40 | 362 | 33533 | 102334155 |
当然用递归求斐波那契数的方法绝对不是一种高效的方法,但能否说明Python对递归优化得不够,还是Python对栈桢的设计策略存在问题?更高效的求斐波那契数的方法如下:
# 用公式求导更快def fabic(n): if(n < 1): raise Exception('the argument can not less than 0') # 辅助数组 result = [0, 1, 1] if(n < 2): return 1 while(n > 2): result.pop(0) result.append(result[0] + result[1]) n = n - 1 else: return result[2]
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
所以,除了在调试环境中,可以执行Thread.join()方法,否则还不如不要启动线程,直接执行方法调用,正确的方法是应该使用CountDownLatch、CyclicBarrier这样的线程工具,如下:
static class FabicTask implements Runnable { private int num; private final CyclicBarrier barrier; public FabicTask(int num, CyclicBarrier barrier) { super(); this.num = num; this.barrier = barrier; } public void run() { fabic(num); try { // 发信号等待结束 this.barrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }}public static void main(String[] args) throws InterruptedException { int NUM = 3; long startTime = System.currentTimeMillis(); CyclicBarrier barrier = new CyclicBarrier(NUM, () -> { // 计算耗费时间 System.out.println(System.currentTimeMillis() - startTime); }); for(int i = 0; i < NUM; i ++) { new Thread(new FabicTask(41, barrier)).start(); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
结论
不要再生产环境中使用Thread.join()方法。