Java虚拟机并发编程-2.并发策略

2.1 分而治之

 

       确定线程数:我们希望至少可以创建处理器核心数那么多个线程。这就保证了有尽可能多的处理器核心可以投入到解决问题的工作中去。通过下面的代码我们可以很容易获取系统可用的处理器核心数:

Runtime.getRuntime().availableProcessors();

       所以应用程序最小线程数应该等于处理器核心数。如果所有的任务都是计算密集型的,则创建处理器可用核心数那么多线程就可以了。如果任务有大于50%的时间处于阻塞状态,则这些任务是IO密集型的,我们就需要创建比处理器核心数大几倍数量的线程。线程数计算公式参考如下:线程数=CPU可用核心数/(1-阻塞系数),其中阻塞系数在0和1之间。计算密集型任务的阻塞系数为0,而IO密集型任务的阻塞系数则接近1。一个完全阻塞的任务是注定要挂掉的,所以我们无需担心阻塞系数会达到1。为了更好的确定所需线程数,我们需要知道下面两个关键参数:

 

  • 处理器可用核心数
  • 任务的阻塞系数

       第一个参数很容易确定,第二个参数可以采用一些性能分析工具或java.lang.management API来确定线程花在系统I/O操作上的时间与CPU密集任务所耗时间的比值。

    

       确定任务的数量:在解决问题的过程中使处理器一直保持忙碌状态比将负载均摊到每个子任务要实惠的多。从处理问题的角度来看,我们需要保证:只要还有待完成的任务,就不能有空闲的处理器核心。

     

        我们必须避免共享可变状态,并用隔离可变性或共享不可变性取而代之。并充分利用现代线程API和线程池。

        Java旧的线程API现在还有用武之地么?

       旧的线程API在功能上有很多缺陷。比如,由于线程不允许被重新启动,所以一旦线程执行晚了我们就必须把Thread类的实例丢掉。于是为了同时处理多个任务,我们通常需要不断的开新线程而不是复用之前已经创建好的。

       像wait()和notify()这样的函数都需要进行线程间同步,我们很难判断何时才是使用它们进行线程间通信的正确时机。而join()函数则使我们的注意力都集中在处理线程消亡的逻辑上,从而忽略了对即将结束的任务的处理。

       此外,synchronized关键字粒度太粗。该关键字没有提供当线程没有获取到锁的情况下的超时逻辑,而且也不允许对互斥区域的并发读。如果我们用了synchronized,那么关于线程安全方面的单元测试页很难进行。

      现在,由Doug Lea等人牵头开发的java.util.concurrent包中的下一代并发API已经很好的替代了旧线程API。

  • 以前代码中使用Thread类及其方法的地方,现在都可以考虑用ExecutorService类及相关类来替代
  • 如果想要更好的控制加锁的过程,则最好使用Lock接口及其方法。
  • 以前代码中使用wait/notify方法的地方,现在都可以使用像CyclicBarrier和CountdownLatch这样的同步工具来替换

猜你喜欢

转载自renhanxiang.iteye.com/blog/2167269
今日推荐