最近Review团队内一些的代码,发现不少地方在使用线程池,但使用比较乱,针对问题建议如下:
-
线程不能调用Thread.stop来停止它,我见过有新员工就这么干过哦,而是需要设置一个标识位,在run方法中判断此标识位退出循环。用interrupt也是可以考虑的,但线程的run方法中要捕获InterruptException。
-
所有线程需要设置Name,主要是方便线程dump出来之后定位问题。这可是编程军规,我们很多的兄弟没有遵守。
-
大多直接是使用Exectors.newXXX直接new线程池,没有设置队列的大小,默认是整型的最大值,一旦有线程处理阻塞,队列上涨,内存不可控制啊。
-
最好不要使用newCachedThreadPool,曾经有个模块这么干,在工作线程处理慢时,线程线会不断上涨没能及时回收。这个模块出现异常,线程dump之出来发现有2000多个由它产生的线程,内存超高。
-
所有线程都要受管理,不允许直接new Thread就直接start就不管了。同样所有从Exectors.newXXX创建的线程池,当bundle去激活时,一定要shutdown。
-
线程个数设置多少合适?不是越多越好,多了竞争资源反而效率低。建议配置的线程数=可用的CPU数/(1-阻塞系数)。阻塞系统在0到1之间,所谓阻塞系数就是发生的IO操作,如读文件,读socket流,读写数据库等占程序时间的比率。这个数值每个系统肯定不一样,可通过分析工具或java.lang.managementAPI来确定这个值,也可以做个估计,然后测试逐步往最佳值靠拢。如果线程不是瓶颈所在,那么大概估一个值就好了。
-
不要在多线程中共享数据,最佳的实践是无锁编程。所谓有锁编程,就是当你需要共享数据的时候,你需要有序的去访问,所有改变共享数据的操作都必须表现出原子的语义,在无锁编程中,并不是说所有操作都是原子的,只有一个很有限的操作集是原子的。采用wait-free 和lock-free 的算法,基于FIFO 的队列和LIFO的栈,或者更复杂的优化级队列、 hash表及红黑树的lock-free 算法以达到无锁编程。
-
定时器中Runnable一定要catch所有异常,否则会由于异常导致定时不再执行。
-
如果你在多个线程之间共享数据了,且采用锁了。那一定要防止出现死锁,什么是死锁:线程A加锁锁a,等待线程B已加锁的锁b释放,而线程B却也要锁a才很能释放锁b,就会发生死锁。平台基于monitor会定时检查死锁,一旦存在死锁,平台会自动重启。
-
不要在构造函数中启动线程,这个会引起什么问题呢?如果有个类B继承了类A,依据java类初始化的顺序,A的构造函数一定会在B的构造函数调用前被调用,那么thread线程也将在B被完全初始化之前启动,当thread运行时使用到了类A中的某些变量,那么就可能使用的不是你预期中的值,因为在B的构造函数中你可能赋给这些变量新的值。也就是说此时将有两个线程(构造线程与新启线程)在使用这些变量,而这些变量却没有同步。
-
你确认你的业务真的需要使用线程池吗?并发异步处理才需要,单线程无阻塞也是效率很高的。多线程在多CPU多核下才有它的真正价值。我们去分析优化时系统时,首先要考虑是减少阻塞,而不是一上来先加几个线程呗。