[java]寻找最优线程数

1.前言 

最近被问到一个问题,"我用java写了一个用到多线程的功能,但是线程数应该多少个比较好呢?"。这个问题以前听的版本有:"CPU核心数的2倍","和CPU核心数一样","CPU核心数加1"。但是因为一个“懒”字将这个问号埋在了心底。为了给这个故事画上一个完美的句号,所以就有了这篇博文。

2.线程定义

   线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。线程是独立调度和分派的基本单位。

在多核或多CPU,或支持Hyper-threading的CPU上使用多线程程序设计的好处是显而易见,即提高了程序的执行吞吐率。在单CPU单核的计算机上,使用多线程技术,也可以把进程中负责IO处理、人机交互而常被阻塞的部分与密集计算的部分分开来执行,编写专门的workhorse线程执行密集计算,从而提高了程序的执行效率。

3.线程与处理器

我们讨论下用一个处理器(单核)的情况下多线程的作用。图1中假设这样一个场景:一个任务要处理一个数据块集合,其中这个任务只有一个线程A,1表示处理第一个数据块,2表示处理第二个数据块。在线程A处理每个数据块的过程中都有一段阻塞的时间。只有线程A工作部分处理器才被使用,也就是说没处理一个数据块,都有一部分计算资源被浪费了。

(图1)

为了充分利用处理器以及加快处理效率,我们引入多线程,如图2。其中共有三个线程A,B,C。当线程A处理第一个数据块遇到阻塞的时候,线程B得到处理器的使用权开始执行。线程B也遇到了阻塞,让出处理器。线程A运气比较好又一次得到处理器后继续执行...线程C在线程A第二次进入阻塞的时候得到处理器使用权执行自己的代码。

(图2)

对比图1和图2中在单处理器单核心的情况下使用多线程可以充分利用系统计算资源,缩短任务整体计算时间。

4.最优线程数计算的理论依据

在上面的图中也许你会有疑问,我这个只是任务中有阻塞部分的场景,阻塞部分和线程数的关系是什么?

Venkat Subramaniam 博士在《Programming Concurrency on the JVM》中提到关于最优线程数的计算,下面是原文:

The minimum number of threads is equal to the number of available
cores. If all tasks are computation intensive, then this is all we need. Having
more threads will actually hurt in this case because cores would be context
switching between threads when there is still work to do. If tasks are IO
intensive, then we should have more threads.

We can compute the total number of threads we’d need as follows:

Number of threads = Number of Available Cores / (1 - Blocking Coefficient)

To determine the number of threads, we need to know two things:
 ? The number of available cores
 ? The blocking coefficient of tasks

The first one is easy to determine; we can look up that information, even at
 runtime, as we saw earlier. It takes a bit of effort to determine the blocking
 coefficient. We can try to guess it, or we can use profiling tools or the java.
 lang.management API to determine the amount of time a thread spends on
 system/IO operations vs. on CPU-intensive tasks.

Blocking Coefficient(阻塞系数) = 阻塞时间/(阻塞时间+使用CPU的时间)

5.实践出真知

对知识的吸收不应只停留在书本上,下面通过下面的示例代码来验证一下这个公式。下面先是代码,然后是实验结果的统计。

Stage 类

package com.bob.testjava.thread;

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

/**
 * Created by zhangmingbo on 1/7/17.
 */
public class Stage {

    public static void main(String[] args) throws InterruptedException {

        long startTime = System.currentTimeMillis();
        ExecutorService threadPool = null;

        try {
            threadPool = Executors.newFixedThreadPool(Constants.THREAD_SIZE);
            CountDownLatch countDownLatch = new CountDownLatch(Constants.SIZE);

            for (int i = 0; i < Constants.THREAD_SIZE; i++) {
                threadPool.execute(new Worker(countDownLatch));
            }

            countDownLatch.await();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            long endTime = System.currentTimeMillis();

            int cpuCoreNum = Runtime.getRuntime().availableProcessors();
            System.out.println("Number of CPU cores :" + cpuCoreNum);
            System.out.println("Work time (millisecond) :" + Constants.WORK_TIME_MILLISECOND);
            System.out.println("Block time (millisecond) :" + Constants.BLOCK_TIME_MILLISECOND);

            Double blockCoefficient = (Constants.BLOCK_TIME_MILLISECOND * 1d) / (Constants.WORK_TIME_MILLISECOND + Constants.BLOCK_TIME_MILLISECOND);
            System.out.println("Block coefficient :" + blockCoefficient);

            int optimalThreadNum = new Double(cpuCoreNum / (1 - blockCoefficient)).intValue();
            System.out.println("Optimal thread number in theory:" + optimalThreadNum);

            System.out.println("Number of Task :" + Constants.SIZE);
            System.out.println("Number of Thread :" + Constants.THREAD_SIZE);
            System.out.println("Cost Time:" + (endTime - startTime));
            try {
                threadPool.shutdownNow();
            } catch (Exception e) {
            }
        }
    }
}

Worker 类

package com.bob.testjava.thread;

import java.util.concurrent.CountDownLatch;

/**
 * Created by zhangmingbo on 1/7/17.
 */
public class Worker implements Runnable {

    private CountDownLatch countDownLatch;

    public Worker(CountDownLatch countDownLatch) {

        this.countDownLatch = countDownLatch;
    }

    public void run() {

        try {
            while (countDownLatch.getCount() > 0) {

                //do work
                TestUtil.doWorkByTime(Constants.WORK_TIME_MILLISECOND);

                //do block operation
                TestUtil.doBlockOperation(Constants.BLOCK_TIME_MILLISECOND);

                countDownLatch.countDown();
            }
        } catch (InterruptedException e) {
        }

    }
}

TestUtil 类

package com.bob.testjava.thread;

import java.util.concurrent.TimeUnit;

/**
 * Created by zhangmingbo on 1/10/17.
 */
public class TestUtil {

    public static void doWorkByTime(long millisecond) {
        long startTime = System.currentTimeMillis();

        while (true) {

            long endTime = System.currentTimeMillis();
            long costTime = endTime - startTime;
            if (costTime >= millisecond) {
                return;
            }

        }

    }

    public static void doBlockOperation(long millisecond) throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(millisecond);
    }

}

Constants 类

package com.bob.testjava.thread;

/**
 * Created by zhangmingbo on 1/7/17.
 */
public class Constants {

    public static final int SIZE = 100;

    public static final int THREAD_SIZE = 8;

    public static final long BLOCK_TIME_MILLISECOND = 5;

    public static final int WORK_TIME_MILLISECOND = 5;

}

上面是本次实验使用的代码,其中 Stage类作为场景类 main方法从这里执行,使用CountDownLatch来充当待处理数据的作用,它的大小决定有多少任务量,当任务做完后会打印出一些信息,如:CPU逻辑核心数、阻塞系数、任务量、任务总耗时等。这里创建了TestUtil类,分别提供类为了模拟占用CPU的操作以及阻塞的部分的方法。可以通过调节Constants类中的参数来统计本次执行的数据。Worker类是本次实验的线程类。

-----------------------------------------------------------------------  分割线  -------------------------------------------------------------------------------

实验结果数据:

实验结果和预期的不太一样。根据公式来说最优线程数应该是8,但是实验结果显示的是8的2倍甚至更多。也许是本身我电脑上还运行着其它的进程,或者我写的程序哪里不合理,这个如果哪位朋友能帮忙指正,不胜感激。

6.总结

寻找最优线程数的过程是快乐的,这快乐来自不断获得新知的喜悦。这篇博文并未完结,尚有谜团没有解开,先记录之:

谜团一:一台服务器中运行着多个进程,计算其中一个进程某个任务的最优线程数肯定要受到干扰,怎么办?

谜团二:如何准确得计算任务的阻塞系数?

7.参考资料

《Programming Concurrency on the JVM》

维基百科

时间: 2024-10-24 11:40:29

[java]寻找最优线程数的相关文章

一次java性能调优总结

我们的系统中新开发了一个数据抽取的功能,东西做完后,一看执行时间那叫一个恼火.参考同类系统同样功能的执行时间,目标:将本地数据处理时间压缩到5秒以内.   第一步: 要想知道哪个地方需要优化,仅凭感觉还是不够,我使用btrace寻找速度慢点原因.下面贴出这次使用的btrace代码: import static com.sun.btrace.BTraceUtils.name; import static com.sun.btrace.BTraceUtils.print; import static

Java Web应用中调优线程池的重要性

不论你是否关注,Java Web应用都或多或少的使用了线程池来处理请求.线程池的实现细节可能会被忽视,但是有关于线程池的使用和调优迟早是需要了解的.本文主要介绍Java线程池的使用和如何正确的配置线程池. 单线程 我们先从基础开始.无论使用哪种应用服务器或者框架(如Tomcat.Jetty等),他们都有类似的基础实现.Web服务的基础是套接字(socket),套接字负责监听端口,等待TCP连接,并接受TCP连接.一旦TCP连接被接受,即可从新创建的TCP连接中读取和发送数据. 为了能够理解上述流

转:Java Web应用中调优线程池的重要性

不论你是否关注,Java Web应用都或多或少的使用了线程池来处理请求.线程池的实现细节可能会被忽视,但是有关于线程池的使用和调优迟早是需要了解的.本文主要介绍Java线程池的使用和如何正确的配置线程池. 单线程 我们先从基础开始.无论使用哪种应用服务器或者框架(如Tomcat.Jetty等),他们都有类似的基础实现.Web服务的基础是套接字 (socket),套接字负责监听端口,等待TCP连接,并接受TCP连接.一旦TCP连接被接受,即可从新创建的TCP连接中读取和发送数据. 为了能够理解上述

Java Web应用调优线程池

最简单的单线程 我们先从基础开始.无论使用哪种应用服务器或者框架(如Tomcat.Jetty等),他们都有类似的基础实现.Web服务的基础是套接字(socket),套接字负责监听端口,等待TCP连接,并接受TCP连接.一旦TCP连接被接受,即可从新创建的TCP连接中读取和发送数据. 为了能够理解上述流程,我们不直接使用任何应用服务器,而是从零开始构建一个简单的Web服务.该服务是大部分应用服务器的缩影.一个简单的单线程Web服务大概是这样的: ServerSocket listener = ne

Java并发(八)计算线程池最佳线程数

目录 一.理论分析 二.实际应用 为了加快程序处理速度,我们会将问题分解成若干个并发执行的任务.并且创建线程池,将任务委派给线程池中的线程,以便使它们可以并发地执行.在高并发的情况下采用线程池,可以有效降低线程创建释放的时间花销及资源开销,如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及“过度切换”(在JVM中采用的处理机制为时间片轮转,减少了线程间的相互切换) . 但是有一个很大的问题摆在我们面前,即我们希望尽可能多地创建任务,但由于资源所限我们又不能创建过多的线程.那么在高

java线程数过高原因分析

作者:鹿丸不会多项式  出处:http://www.cnblogs.com/hechao123   转载请先与我联系. 一.问题描述 前阵子我们因为B机房故障,将所有的流量切到了A机房,在经历了推送+自然高峰之后,A机房所有服务器都出现java线程数接近1000的情况(1000是设置的max值),在晚上7点多观察,java线程数略有下降,但还是有900+的样子,而此时,单台服务器的TPS维持在400/s,并不是一个特别大的量.然后将A机房一台机器下线,继续观察,到了晚上9点多,那台下线的机器,j

Java并发工具类(三)控制并发线程数的Semaphore

作用 Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源. 简介 Semaphore也是一个线程同步的辅助类,可以维护当前访问自身的线程个数,并提供了同步机制.使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数. 主要方法摘要: void acquire():从此信号量获取一个许可,在提供一个许可前翼子将线程阻塞,否则线程被中断. void release():释放一个许可,将其返回给信号量. in

cpu个数、核数、线程数、Java多线程关系的理解+物理cpu数和cpu核数和逻辑cpu数和vcpu区别

1.cpu个数.核数.线程数.Java多线程关系的理解 URL地址:https://blog.csdn.net/helloworld0906/article/details/905471592.物理cpu数和cpu核数和逻辑cpu数和vcpu区别 URL地址:https://blog.csdn.net/budonglaoshi123/article/details/84325720 原文地址:https://www.cnblogs.com/curedfisher/p/12204102.html

java线程池如何合理配置核心线程数

线程池合理的线程数你是如何考虑的?: 1.先看下机器的CPU核数,然后在设定具体参数: System.out.println(Runtime.getRuntime().availableProcessors()); 即CPU核数 = Runtime.getRuntime().availableProcessors() 2.分析下线程池处理的程序是CPU密集型,还是IO密集型 CPU密集型:核心线程数 = CPU核数 + 1 IO密集型:核心线程数 = CPU核数 * 2 注:IO密集型(某大厂实