应用调优常用技巧-線程池

  • 应用调优常用技巧 - 线程池
    • 線程池的好處
    • 核心API-操作類
    • 核心API-監控類
    • 2-2 线程池BlockingQueue详解、选择与调优
      • 調優技巧
    • 2-3 线程池ScheduledThreadPoolExecutor详解
    • 2-4 线程池ForkJoinPool详解
    • 2-5 线程池Executors讲解
    • 2-6 线程池调优实战
      • 1.线程数调优
      • 2. BlockingQueue調優
    • 2-7 線程池總結

应用调优常用技巧 - 线程池

線程池的應用:

  1. Eureka Client里面,每隔30秒发送个心跳,其实就是个定时任务型的线程池;
    Tomcat处理请求时,也有线程池;
  2. Spring Task也是用的线程池实现的定时任务
  3. 你用@Async实现异步的时候,底层也是个线程池,当然视频里讲了@Async以及手动用线程池实现异步两种方式。
  4. 比如你要处理一批数据,比如100万,就可以弄个线程池,每个线程处理一部分数据,并发进行,从而提升处理速度。

線程池的好處

  1. 重用已存在的縣城
  2. 控制并發
  3. 功能強大

核心API-操作類

用的最多的是:execute和submit

  1. execute():提交任務,交給線程池執行
  2. submit():提交任務,能夠返回執行結果
  3. shutdown():關閉線程池,等待任務都執行
  4. shutdownNow():關閉線程池,不等待任務執行完(很少使用,因為過於暴力了)

核心API-監控類

  1. getTaskCount():返回線程已執行和未執行的任務總數
  2. getComplatedTaskCount():已完成的任務總數
  3. getPollSize():線程池當前線程的數量
  4. getActiveCount():線程池中正在執行任務的線程數量

2-2 线程池BlockingQueue详解、选择与调优



API中啊add的相關代碼

public class LinkedBlockingQueueTest {public static void main(String[] args) {LinkedBlockingQueue<Object> queue =new LinkedBlockingQueue<>(1);queue.add("abc");boolean def = queue.offer("def");System.out.println(def);queue.add("g");}
}
無界隊列消耗很大,因為他沒有界限。

調優技巧

  • 合理设置corePoolSize、maximumPoolSize、 workQueue的容量。
    下面代碼中corePoolSize=5,maximumPoolSize=10(不是10L),workQueue=100。 這樣多餘的任務會在隊列裡面排隊,線程的個數會保持在一個比較合理的範圍。這樣線程的開銷就比較小了。
  • 假如經常發生阻塞,說明隊列經常滿,可以考慮重新設置線程數量executor.setMaximumPoolSize(50);
  • 假如隊列的容量設置的比較小, 通常需要把線程池的容量設置的大一點,這樣cpu的使用率會高一點。
  • 如果線程池的容量設置的非常大,而隊列比較小,二任務提交的非常多的時候,就會創建很多個線程去執行,此時會降低系統的吞吐量,因為線程切換的開銷變大了。
public static void main(String[] args) throws ExecutionException, InterruptedException {ThreadPoolExecutor executor =new ThreadPoolExecutor(5,10,// 默认情况下指的是非核心线程的空闲时间// 如果allowCoreThreadTimeOut=true:核心线程/非核心线程允许的空闲时间10L,TimeUnit.SECONDS,new LinkedBlockingQueue<>(100),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());executor.allowCoreThreadTimeOut(true);// 核心线程 -> 正式员工// 非核心线程 -> 临时工executor.execute(new Runnable() {@Overridepublic void run() {System.out.println("线程池测试");}});executor.execute(new Runnable() {@Overridepublic void run() {System.out.println("线程池测试2");}});Future<String> future = executor.submit(new Callable<String>() {@Overridepublic String call() throws Exception {return "测试submit";}});String s = future.get();System.out.println(s);}

2-3 线程池ScheduledThreadPoolExecutor详解


实际项目中应用比较多的是下面这两个方法

  • scheduleAtFixedRate
  • ScheduleAtFixedRate
public class ScheduledThreadPoolExecutorTest {public static void main(String[] args) throws ExecutionException, InterruptedException {ScheduledThreadPoolExecutor executor= new ScheduledThreadPoolExecutor(10,Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
//    // 延时3秒之后再去执行任务
//    executor.schedule(
//      new Runnable() {//        @Override
//        public void run() {//          System.out.println("aaa");
//        }
//      },
//      3,
//      TimeUnit.SECONDS
//    );
//
//    // 延时4秒之后再去执行任务,可以返回执行结果
//    ScheduledFuture<String> future = executor.schedule(new Callable<String>() {//      @Override
//      public String call() throws Exception {//        return "bbb";
//      }
//    }, 4, TimeUnit.SECONDS);
//    String s = future.get();
//    System.out.println(s);executor.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {System.out.println("scheduleAtFixedRate" + new Date());try {Thread.sleep(1000L);} catch (InterruptedException e) {e.printStackTrace();}}},// 第一次执行任务时,延时多久0,// 每个多久执行这个任务3, TimeUnit.SECONDS);executor.scheduleWithFixedDelay(new Runnable() {@Overridepublic void run() {System.out.println("scheduleWithFixedDelay" + new Date());try {Thread.sleep(1000L);} catch (InterruptedException e) {e.printStackTrace();}}},// 第一次执行任务时,延时多久0,// 每次执行完任务之后,延迟多久再次执行这个任务3, TimeUnit.SECONDS);}
}

Timer与ScheduledThreadPoolExecutor对比,博主写的很好
对比结果:Timer是单线程的,在实际项目中要尽量使用ScheduledThreadPoolExecutor,慎用Timer

2-4 线程池ForkJoinPool详解

ForkJoinPoll在實際項目中使用並不多,但是可以幫助我們閱讀JDK源碼。(鄙人並不懂)

public class ForkJoinPoolTest {// ForkJoinPool实现1-100的求和public static void main(String[] args) throws ExecutionException, InterruptedException {ForkJoinPool pool = new ForkJoinPool();ForkJoinTask<Integer> task = pool.submit(new MyTask(1, 100));Integer sum = task.get();System.out.println(sum);}
}class MyTask extends RecursiveTask<Integer> {public MyTask(int start, int end) {this.start = start;this.end = end;}// 当前任务计算的起始private int start;// 当前任务计算的结束private int end;// 阈值,如果end-start在阈值以内,那么就不用再去细分任务public static final int threshold = 2;@Overrideprotected Integer compute() {int sum = 0;boolean needFork = (end - start) > threshold;if (needFork) {int middle = (start + end) / 2;MyTask leftTask = new MyTask(start, middle);MyTask rightTask = new MyTask(middle + 1, end);// 执行子任务leftTask.fork();rightTask.fork();// 子任务执行完成之后的结果Integer leftResult = leftTask.join();Integer rightResult = rightTask.join();sum = leftResult + rightResult;} else {for (int i = start; i <= end; i++) {sum += i;}}return sum;}
}

2-5 线程池Executors讲解

創建線程池的工廠。

public class ExecutorsTest {public static void main(String[] args) {// 可以選擇上圖中不同的線程池ExecutorService pool = Executors.newCachedThreadPool();pool.execute(new Runnable() {@Overridepublic void run() {System.out.println("aaa");}});}
}

2-6 线程池调优实战

線程池調優

  1. 线程数调优
  2. BlockingQueue调优

1.线程数调优

任务可以分為以下幾幾種

  1. cpu密集型任務這個任務大部分時間都在進行cpu計算,如排序
    調優經驗公式:N+1

查看本機器cpu數量:

public class ThreadPoolCoreTest {public static void main(String[] args) {int i = Runtime.getRuntime().availableProcessors();System.out.println(i);}
}

輸出結果是8

8
Process finished with exit code 0

那麼就可以根據公式:N+1,就可以將線程的數量設置為 9
為什麼設置為9呢,8不好嗎?如果設置為8個線程,某一個線程突然出現暫停或者中斷的話,那麼8個cpu就會有一個出現空閒,多出來的那個線程,就會充分利用cpu的空閒時間。就像踢足球有一個替補隊員。

  1. IO密集型任務大部分時間在和IO交互,如操作數據庫
    調優經驗公式:2N。
    線程處理IO,是不佔用cpu的,所以處理IO的時候這個線程的CPU資源,就可以交給其他線程去使用,因此就可以多設置一些線程,業界比較認可的經驗值是2N,對於我的機器線程數就可以配製成16
  2. 混合型任務實際項目中,混合型任務比較多
    調優公式:N * U *(1+WT/ST)

    N:非常好獲取
    U:是我們的期待,也非常好設置
    WT和ST怎麼獲取呢?技巧:我們可以運行一個項目,然後打開終端,輸入jvisualvm命令,就可以打開java VisualVM了

    然後選擇自己剛運行的項目,雙擊打開,然後Profile–>cpu,性能分析會運行一段時間。這樣ST和WT就知道了

2. BlockingQueue調優

得做估算,做下面兩個估算。這樣比較麻煩,可以使用懶人工具

  1. 單個任務佔用內存
  2. 線程池計劃佔用內存

懶人工具,裡面有一個工具類,可以幫助我們調優線程池

https://www.javacodegeeks.com/2012/03/threading-stories-about-robust-thread.html

線程池調優工具類:


import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.BlockingQueue;/*** 線程池調優工具類* A class that calculates the optimal thread pool boundaries. It takes the desired target utilization and the desired* work queue memory consumption as input and retuns thread count and work queue capacity.* * @author Niklas Schlimm* */
public abstract class PoolSizeCalculator {/*** The sample queue size to calculate the size of a single {@link Runnable} element.*/private final int SAMPLE_QUEUE_SIZE = 1000;/*** Accuracy of test run. It must finish within 20ms of the testTime otherwise we retry the test. This could be* configurable.*/private final int EPSYLON = 20;/*** Control variable for the CPU time investigation.*/private volatile boolean expired;/*** Time (millis) of the test run in the CPU time calculation.*/private final long testtime = 3000;/*** Calculates the boundaries of a thread pool for a given {@link Runnable}.* * @param targetUtilization*            the desired utilization of the CPUs (0 <= targetUtilization <= 1)* @param targetQueueSizeBytes*            the desired maximum work queue size of the thread pool (bytes)*/protected void calculateBoundaries(BigDecimal targetUtilization, BigDecimal targetQueueSizeBytes) {calculateOptimalCapacity(targetQueueSizeBytes);Runnable task = creatTask();start(task);start(task); // warm up phaselong cputime = getCurrentThreadCPUTime();start(task); // test intervallcputime = getCurrentThreadCPUTime() - cputime;long waittime = (testtime * 1000000) - cputime;calculateOptimalThreadCount(cputime, waittime, targetUtilization);}private void calculateOptimalCapacity(BigDecimal targetQueueSizeBytes) {long mem = calculateMemoryUsage();BigDecimal queueCapacity = targetQueueSizeBytes.divide(new BigDecimal(mem), RoundingMode.HALF_UP);System.out.println("Target queue memory usage (bytes): " + targetQueueSizeBytes);System.out.println("createTask() produced " + creatTask().getClass().getName() + " which took " + mem+ " bytes in a queue");System.out.println("Formula: " + targetQueueSizeBytes + " / " + mem);System.out.println("* Recommended queue capacity (bytes): " + queueCapacity);}/*** Brian Goetz' optimal thread count formula, see 'Java Concurrency in Practice' (chapter 8.2)* * @param cpu*            cpu time consumed by considered task* @param wait*            wait time of considered task* @param targetUtilization*            target utilization of the system*/private void calculateOptimalThreadCount(long cpu, long wait, BigDecimal targetUtilization) {BigDecimal waitTime = new BigDecimal(wait);BigDecimal computeTime = new BigDecimal(cpu);BigDecimal numberOfCPU = new BigDecimal(Runtime.getRuntime().availableProcessors());BigDecimal optimalthreadcount = numberOfCPU.multiply(targetUtilization).multiply(new BigDecimal(1).add(waitTime.divide(computeTime, RoundingMode.HALF_UP)));System.out.println("Number of CPU: " + numberOfCPU);System.out.println("Target utilization: " + targetUtilization);System.out.println("Elapsed time (nanos): " + (testtime * 1000000));System.out.println("Compute time (nanos): " + cpu);System.out.println("Wait time (nanos): " + wait);System.out.println("Formula: " + numberOfCPU + " * " + targetUtilization + " * (1 + " + waitTime + " / "+ computeTime + ")");System.out.println("* Optimal thread count: " + optimalthreadcount);}/*** Runs the {@link Runnable} over a period defined in {@link #testtime}. Based on Heinz Kabbutz' ideas* (http://www.javaspecialists.eu/archive/Issue124.html).* * @param task*            the runnable under investigation*/public void start(Runnable task) {long start = 0;int runs = 0;do {if (++runs > 5) {throw new IllegalStateException("Test not accurate");}expired = false;start = System.currentTimeMillis();Timer timer = new Timer();timer.schedule(new TimerTask() {public void run() {expired = true;}}, testtime);while (!expired) {task.run();}start = System.currentTimeMillis() - start;timer.cancel();} while (Math.abs(start - testtime) > EPSYLON);collectGarbage(3);}private void collectGarbage(int times) {for (int i = 0; i < times; i++) {System.gc();try {Thread.sleep(10);} catch (InterruptedException e) {Thread.currentThread().interrupt();break;}}}/*** Calculates the memory usage of a single element in a work queue. Based on Heinz Kabbutz' ideas* (http://www.javaspecialists.eu/archive/Issue029.html).* * @return memory usage of a single {@link Runnable} element in the thread pools work queue*/public long calculateMemoryUsage() {BlockingQueue<Runnable> queue = createWorkQueue();for (int i = 0; i < SAMPLE_QUEUE_SIZE; i++) {queue.add(creatTask());}long mem0 = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();long mem1 = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();queue = null;collectGarbage(15);mem0 = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();queue = createWorkQueue();for (int i = 0; i < SAMPLE_QUEUE_SIZE; i++) {queue.add(creatTask());}collectGarbage(15);mem1 = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();return (mem1 - mem0) / SAMPLE_QUEUE_SIZE;}/*** Create your runnable task here.* * @return an instance of your runnable task under investigation*/protected abstract Runnable creatTask();/*** Return an instance of the queue used in the thread pool.* * @return queue instance*/protected abstract BlockingQueue<Runnable> createWorkQueue();/*** Calculate current cpu time. Various frameworks may be used here, depending on the operating system in use. (e.g.* http://www.hyperic.com/products/sigar). The more accurate the CPU time measurement, the more accurate the results* for thread count boundaries.* * @return current cpu time of current thread*/protected abstract long getCurrentThreadCPUTime();}

示例類,需要繼承上面的調優工具類,運行一下會得到結果。
其中的Runnable 方法,指的就是我們實際項目中需要運行的任務,然後由他來估算,運行這些任務的線程線程數應該配多大,BlockingQueue應該配置多大

public class MyPoolSizeCalculator extends PoolSizeCalculator {public static void main(String[] args) {MyPoolSizeCalculator calculator = new MyPoolSizeCalculator();calculator.calculateBoundaries(// CPU目标利用率new BigDecimal(1.0),// blockingqueue占用的内存大小,bytenew BigDecimal(100000));ThreadPoolExecutor executor =new ThreadPoolExecutor(8,8,// 默认情况下指的是非核心线程的空闲时间// 如果allowCoreThreadTimeOut=true:核心线程/非核心线程允许的空闲时间10L,TimeUnit.SECONDS,new LinkedBlockingQueue<>(2500),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());}protected long getCurrentThreadCPUTime() {// 当前线程占用的总时间return ManagementFactory.getThreadMXBean().getCurrentThreadCpuTime();}protected Runnable creatTask() {return new AsynchronousTask();}protected BlockingQueue createWorkQueue() {return new LinkedBlockingQueue<>();}}class AsynchronousTask implements Runnable {@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}
}

運行結果如下,建議線程數配置成8(* Optimal thread count: 8)

Target queue memory usage (bytes): 100000
createTask() produced com.imooc.jvm.threadpool.AsynchronousTask which took 40 bytes in a queue
Formula: 100000 / 40
* Recommended queue capacity (bytes): 2500
Number of CPU: 8
Target utilization: 1
Elapsed time (nanos): 3000000000
Compute time (nanos): 3000000000
Wait time (nanos): 0
Formula: 8 * 1 * (1 + 0 / 3000000000)
* Optimal thread count: 8Process finished with exit code 0

計算BlockingQueue,把示例類中的System.out.println(Thread.currentThread().getName());注釋掉,再次運行就可以了。為什麼要注釋掉呢?我也不清楚。實際用的過程中,我們需要把AsynchronousTask中的方法設置為空。
運行結果如下:建議設置成2500()

Target queue memory usage (bytes): 100000
createTask() produced com.imooc.jvm.threadpool.AsynchronousTask which took 40 bytes in a queue
Formula: 100000 / 40
* Recommended queue capacity (bytes): 2500
Number of CPU: 8
Target utilization: 1
Elapsed time (nanos): 3000000000
Compute time (nanos): 2984375000
Wait time (nanos): 15625000
Formula: 8 * 1 * (1 + 15625000 / 2984375000)
* Optimal thread count: 8

自己的項目需要根據實際情況進行計算,比如:
業務評估,之後根據公式進行計算,這個上面有說到。
逐步調整的話,就是通過壓測,將工具計算出來的結果,比如說為8,我們可以在測試一下7,9,10等,在公式計算出來的結果周邊進行滑動,多次對比,然後找到最優解。

2-7 線程池總結

需要掌握的有一下內容,可以幫助我們應付面試,在實際工作中也會有用。

应用调优常用技巧-線程池相关推荐

  1. mysql 开启 thread pool_MySQL線程池(THREAD POOL)的處理

    背景介紹 MySQL常用(目前線上使用)的線程調度方式是one-thread-per-connection(每連接一個線程),server為每一個連接創建一個線程來服務,連接斷開后,這個線程進入thr ...

  2. 这些SQL调优小技巧,你学废了吗?

    推荐:本文转载自"老虎刘".敢于对技术网红文提出质疑,并给出有效评论和批复,刘哥走在了我们前面. Oracle 原厂优化组组长,严谨治学,敬畏技术,在刘哥身上,你可以看到热情,热血 ...

  3. jvm监控调优常用命令

    jvm监控调优常用命令 转载于:https://www.cnblogs.com/likun10579/p/6403324.html

  4. Java進階:ExecutorService 線程池

    Java 進階:ExecutorService 線程池 文章目錄 Java 進階:ExecutorService 線程池 簡介 參考 正文 Thread 野線程 繼承 Thread 實現 Runnab ...

  5. C# 連接mysql,連接后顯示多個線程池

    數據庫連接字符串 return string.Concat(new string[]             {                 "Database='",     ...

  6. 自定義 ForkJoinPool 線程池,并消除classLoader加载失败的问题

    自定義 ForkJoinPool 線程池,并消除classLoader加载失败的问题 添加 setContextClassLoader 写入classLoader 信息 import java.uti ...

  7. Mysql性能调优常用参数配置

    全文中一共有常用的(事实上你如果花1-2周阅读.理解.自己动手设一下后是需要这么多参数的)76个参数 可能你从未看到过这样的一篇集中火力式的把mysql参数列了这么全的文章.因此这一篇是汇集了最精华和 ...

  8. Tomcat 调优的技巧

    描述 最近在补充自己的短板,刚好整理到Tomcat调优这块,基本上面试必问,于是就花了点时间去搜集一下tomcat调优 都调了些什么,先记录一下调优手段,更多详细的原理和实现以后用到时候再来补充记录, ...

  9. JVM调优常用参数配置

    2019独角兽企业重金招聘Python工程师标准>>> 堆配置 -Xms:初始堆大小 -Xms:最大堆大小 -XX:NewSize=n:设置年轻代大小 -XX:NewRatio=n: ...

最新文章

  1. 近期AI领域8篇精选论文(附论文、代码)
  2. (经典)Hibernate多对多关系映射(五)
  3. 为新研究准备好一块用武之地:最全任务型对话数据调研
  4. FPGA开发经验谈-FPGA 设计的四种常用思想与技巧(二)
  5. PyQt5笔记(07) -- 变换控件颜色
  6. 阿里云下mysql远程访问被拒绝_记一次MySQL数据库拒绝访问的解决过程
  7. L1-005. 考试座位号-PAT团体程序设计天梯赛GPLT
  8. 语言怎么得到直流电压并采样_热点|昆明专业12V10A开关电源怎么选
  9. WSUS提示“内容文件下载失败”的修复方法
  10. 第二章 让你的kali系统变得更好用
  11. 弃用 Notepad++,这款开源替代品更牛逼!
  12. 测试工程师进阶之测试用例发散思维(二)
  13. [精简整理]疏通中国历史脉络——“隋、唐(五代十国)”篇
  14. 微信公众号如何进行账号迁移?
  15. JQuery快速入门之插件
  16. (七)《数电》——CMOS与TTL门电路
  17. zabbix连接mysql_zabbix的简单操作(监控客户端MySQL数据包库)
  18. php-fpm的重启方法
  19. python“反反爬虫”
  20. 基础练习21- Sine之舞(python答案)

热门文章

  1. 勇士大战恶魔?这款桌游明明是套高质量原创手办
  2. 人民币美元兑换程序python简单_纯代码实现人民币兑换美元
  3. 观展指南|《星火·新生》沉浸式体验展倒计时1天
  4. 【睡服】自动化面试官,就用2020年最全的自动化测试面试题及答案
  5. 海康威视 0day_清华紫光原厂3D TLC颗粒初体验,海康威视C2000 PRO 2TB版体验
  6. CPU降频实现原理与试验数据
  7. 【算法】求n的m次方(快速幂取模)
  8. JAVA的一些学习方法
  9. 如何面试3w的java工程师的成功秘籍
  10. 手机如何制作gif?简单三步在线合成gif动图