从底层角度

image.png

  • T1线程执行的时候将T1的指令和数据放入CPU,CPU寄存计算单元进行计算
  • 换另一个线程时,先将T1的数据和指令放进缓存中(先理解为内存也行)===>保护现场
  • cpu只负责计算,不管是哪个线程,负责管理的是老大—-操作系统OS
  • OS进行线程切换时也要消耗系统资源
  • 线程切换的专用名词:context switch线程上下文切换
  • 线程切换也需要资源!!!===>有的面试题就是从线程切换需要资源这个角度来出的!

从线程切换需要资源这个角度出的面试题

  1. 单核cpu设定多线程是否有意义?
    1. 有意义
    2. 线程是一段小程序做的一些操作,不是所有的操作都要cpu的(比如进行io、等待网络传过来的数据),这时就可以将cpu让给别的线程来执行
    3. 这时能充分利用cpu资源
    4. 不消耗cpu或者sleep睡着了
    5. 线程按照执行的分类:
      1. cpu密集型:大量时间做计算,比如for循环、加减乘除等计算(cpu利用率高)
      2. io密集型:大量时间等待输入输出的内容,等到数据之后做一个简单的拷贝等简单消耗cpu的操作,大部分时间在等数据的输入输出
      3. 上述两种是不同的线程,但大部分线程计算和io都有
      4. 不同种类的线程要设定不同的数量,是有区别的(见下一个问题—->稍复杂)
      5. 周老师的IO课也可以看起来了!
  2. 线程数是不是越大越好?
    1. 不是,线程之间的切换也要消耗资源
    2. 线程数量特别大,cpu资源会浪费在资源的来回切换上
    3. 故不是越大越好
    4. 合适的线程数量,不多也不少!(少了效率低,多了切换浪费cpu资源)
    5. 例子:计算10亿个数的和(分段给多线程计算在求和)
      1. m1方法单线程
      2. m2方法两个线程(join)
      3. m3方法使用threadCount常量来决定使用的线程数(可改变)(CountDownLatch倒计时门闩)
      4. 倒计时门闩和循环栅栏 ```java package com.mashibing.juc.c_000_threadbasic;

import com.mashibing.insidesync.T;

import java.text.DecimalFormat; import java.util.Random; import java.util.concurrent.CountDownLatch;

public class T01_MultiVSSingle_ContextSwitch { //=================================================== private static double[] nums = new double[10_0000_0000]; private static Random r = new Random(); private static DecimalFormat df = new DecimalFormat(“0.00”);

  1. static {
  2. for (int i = 0; i < nums.length; i++) {
  3. nums[i] = r.nextDouble();
  4. }
  5. }
  6. private static void m1() {
  7. long start = System.currentTimeMillis();
  8. double result = 0.0;
  9. for (int i = 0; i < nums.length; i++) {
  10. result += nums[i];
  11. }
  12. long end = System.currentTimeMillis();
  13. System.out.println("m1: " + (end - start) + " result = " + df.format(result));
  14. }
  15. //=======================================================
  16. static double result1 = 0.0, result2 = 0.0, result = 0.0;
  17. private static void m2() throws Exception {
  18. Thread t1 = new Thread(() -> {
  19. for (int i = 0; i < nums.length / 2; i++) {
  20. result1 += nums[i];
  21. }
  22. });
  23. Thread t2 = new Thread(() -> {
  24. for (int i = nums.length / 2; i < nums.length; i++) {
  25. result2 += nums[i];
  26. }
  27. });
  28. long start = System.currentTimeMillis();
  29. t1.start();
  30. t2.start();
  31. t1.join();
  32. t2.join();
  33. result = result1 + result2;
  34. long end = System.currentTimeMillis();
  35. System.out.println("m2: " + (end - start) + " result = " + df.format(result));
  36. }
  37. //===================================================================
  38. private static void m3() throws Exception {
  39. final int threadCount = 32;
  40. Thread[] threads = new Thread[threadCount];
  41. double[] results = new double[threadCount];
  42. final int segmentCount = nums.length / threadCount;
  43. CountDownLatch latch = new CountDownLatch(threadCount);
  44. for (int i = 0; i < threadCount; i++) {
  45. int m = i;
  46. threads[i] = new Thread(() -> {
  47. for (int j = m * segmentCount; j < (m + 1) * segmentCount && j < nums.length; j++) {
  48. results[m] += nums[j];
  49. }
  50. latch.countDown();
  51. });
  52. }
  53. double resultM3 = 0.0;
  54. long start = System.currentTimeMillis();
  55. for (Thread t : threads) {
  56. t.start();
  57. }
  58. latch.await();
  59. for (int i = 0; i < results.length; i++) {
  60. resultM3 += results[i];
  61. }
  62. long end = System.currentTimeMillis();
  63. System.out.println("m3: " + (end - start) + " result = " + df.format(result));
  64. }
  65. public static void main(String[] args) throws Exception {
  66. m1();
  67. m2();
  68. m3();
  69. }

} ```

  1. 工作线程数(线程池中线程数量)设多少合适?(最难的,不容易回答好的问题)
    1. 确定要使用多线程了,那么请多少个工人是最合适的是效率最高的?该如何计算
    2. 试来试去来决定一个最小的值===>用压测来决定(实际中这种方式经常使用)
      1. 根据实际情况(机器的配置、cpu的核数等)来决定一个大概的初始值
      2. 然后再去试===>效率高一点(核恰好都利用上是效率最高的===>32核就用32个线程)
    3. 根据cpu的计算能力来设定你需要多少个线程合适
    4. 只按cpu核数来设定也不尽然
      1. 因为一台机上除了自己的程序外还有别的其他线程、进程
      2. 不一定只有自己跑的线程,要看机器的实际情况
    5. 出于安全的角度===>要给cpu留一点余量
      1. 不能让每一个cpu利用率都到100%
      2. 再来其他线程就运行不了,差不多达到80%左右就成了
      3. 假如有特殊紧急的情况还有20%的cpu可以用
    6. 在实际中更多的是模仿生产环境会产生的一些情况来做压测找到一个合适的线程数
    7. 面试与面试官聊的线程数量的设计公式

image.png

  1. 1. 网上有很多变种的公式===>实际上都是一样的(原理一样)
  2. 1. 50%的时间做计算,其余50%的时间在wait===>这时只要2个线程就可以了,第一个线程等待的时候第二个线程算,第二个线程等待的时候第一个线程算===>充分利用cpu100%
  3. 1. 考虑因素:多长时间在等待,多长时间在计算,利用cpu到百分之多少,有几颗cpu
  4. 1. 假如上例有两颗cpu,则四个线程可利用到100%
  5. 1. 上述公式:线程数=cpu数*期望cpu利用率*(1+等待时间/计算时间(利用时间))
  6. 1. 大厂面试官追问:**我怎么知道等待时间和计算时间之比呢?我调用了个wait,我怎么知道他wait了多长时间?**
  7. 1. **只有部署上去,运行起来之后,通过统计才能得到大概的数值**
  8. 1. **使用工具来获取这个数值===>****profiler**
  9. 1. **多长时间在等待另一方法、多长时间在自己做计算、多长时间是在等待网络、系统瓶颈在哪里?===>都可以分析出来**
  10. 1. **profiler是一统称,即性能分析工具;java常用JProfiler(好用但收费);有免费的,自己用日志也行(前后时间相减)**
  11. 1. **部署服务器上了,在生产环境中还是有些不一样,这时使用Arthas(阿尔萨斯)===>压测**
  12. 1. **分布式调用链路,如何找瓶颈?===>分布式的内容(现在只谈单机)**


🤏随想

  1. 线程的原子性与事务的原子性之间的区别与联系
    1. 前者保证执行中没有别的线程打扰?
    2. 后者保证要么成功,要么失败?
  2. 线程的原子性是指只要线程开始执行,那么就一定会执行完,不会发生让出cpu等待其他线程的状态,即其他线程不能打断该线程