java线程(一)

1.线程的历史(CPU效率压榨的历史)

  单进程人工切换 -纸带机 CPU利用率不高,CPU在等人切换纸带.

  多进程批处理 -多个任务批量执行 ABCDE5个任务(一个QQ,一个微信,一个传奇,一个QQ音乐,一个WPS)批量执行,A卡住了,后面BC...都不能执行.

  多进程并行处理- 把程序写在不同的内存位置上来回切换.

  多线程-一个程序内部不同任务的切换.

     -selector - epoll

  纤程(java)/协程(go)-绿色线程,用户管理的线程(不是OS管理的). 

  1.1 什么是程序:可执行文件,qq.exe

  一个程序是可以在内存中放很多份的.每一份都是一个进程.

  一个程序QQ.exe,双击的时候,把它读到内存里面为一个进程.

  1.2 什么是进程,操作系统找到磁盘上的QQ.exe ,并把它读到内存里面.操作系统为每一个进程分配一些资源,比如说内存空间、文件描述符、IO端口号等等.程序就放在硬盘上,真到他开始执行的时候,才把他放到内存里.放到内存后,哪个程序要执行,把他放到CPU去执行.一个程序有多个进程,但代码可以控制成只有一个进程,都可以。进程:操作系统进行资源分配的基本单位。是一个静态单位.

       

 

      1.3 什么是线程:一个程序的不同执行路径 ,多线程一个程序有多个不同的执行路径. 程序是以线程为单位开始执行的,操作系统会找到我们的主线程main方法,扔给CPU去执行,如果主线程开启了别的线程 ,CPU  A线程执行一会交给CPU,B线程执行交给CPU一会会产生线程,就产生了线程的切换. 线程是在进程的内部,是调度执行的基本单位。线程是一个动态的概念,多个线程共享同一个进程里面的所有资源.很多麻烦事在共享资源里面开始产生.

     1.4 线程切换

    CPU的3个重要组件:

    ALU: 计算单元.

    PC: 寄存器 ,记录到底执行到哪条指令了.

    registers:  寄存器组,存放数据.  

当T1线程运行的时候,把T1的指令和数据放进CPU,然后CPU计算单元对他进行计算,该做输出作输出,该做其他操作做其他操作,那么假如说根据操作系统的线程调度的算法,这个线程已经到他的时间了,CPU不能再继续服务你了,该服务另外一个线程了,现在是T1的线程,它的数据在计存器里,然后对它来进行计算,那么要换另外一个线程的时候,

把T1线程的数据和指令指令地址存放到缓存里面去,再把另外一个线程T2,它相关的数据,指令放进来,让CPU继续来做计算,所以CPU是一个特别傻的东西,他就管算,根据这条指令算这些数据,他其他什么都不管,至于说这条指令和这个数据是属于谁的,属于哪个线程的,老大操作系统的事情,那,比如说我T2的时间片也到了,我又想让T1回来继续做计算,应该知道把T2的,放到缓存里。

把原来T1的缓存好的数据再放回来,好CPU又是傻傻的做计算,当然中间需要经过操作系统的调度过程呢,操作系统这个调度呢,也是需要消耗系统资源的,所以线程切换就是这么一个过程,它的专业名词叫context,Switch线程上下文切换,线程切换的这个东西也是需要消耗资源的。

2. 单核CPU设定多线程是否有意义.

  一种线程叫CPU密集型,线程在做大量的计算,这种对CPU利用率比较高.

  还有一种叫IO密集型,大量的时间来等待输入输出的内容,大量的时间在等.

3. 线程数是不是越大越好.

  线程的切换也是需要时间的,比如1000个线程CPU的时间都用来1000个线程切换上了.因为每个线程都需要CPU 轮流来执行的,我在这1000个线程里面切来切去,切来切去,本身就需要消耗更大的资源,所以并不是越大越好.

        

    private static double[] nums = new double[10_0000_0000];
    private static Random r = new Random();
    private static DecimalFormat df = new DecimalFormat("0.00");

    static {
        for (int i = 0; i < nums.length; i++) {
            nums[i] = r.nextDouble();
        }
    }
   //单线程计算
  private static void m1() { long start = System.currentTimeMillis(); double result = 0.0; for (int i = 0; i < nums.length; i++) { result += nums[i]; } long end = System.currentTimeMillis(); System.out.println("m1: " + (end - start) + " result = " + df.format(result)); }
//启动两个线程,效率比单线程提升将近1倍.
static double result1 = 0.0, result2 = 0.0, result = 0.0; private static void m2() throws Exception { Thread t1 = new Thread(() -> { for (int i = 0; i < nums.length / 2; i++) { result1 += nums[i]; } }); Thread t2 = new Thread(() -> { for (int i = nums.length / 2; i < nums.length; i++) { result2 += nums[i]; } }); long start = System.currentTimeMillis(); t1.start(); t2.start(); t1.join(); t2.join(); result = result1 + result2; long end = System.currentTimeMillis(); System.out.println("m2: " + (end - start) + " result = " + df.format(result)); }

3. 工作线程数(线程池中的线程数)设多少合适 ?比较难.

    private static void m3() throws Exception {
        final int threadCount = 32;
        Thread[] threads = new Thread[threadCount];
        double[] results = new double[threadCount];
        final int segmentCount = nums.length / threadCount;
        CountDownLatch latch = new CountDownLatch(threadCount);
        for (int i = 0; i < threadCount; i++) {
            int m = i;
            threads[i] = new Thread(() -> {
                for (int j = m * segmentCount; j < (m + 1) * segmentCount && j < nums.length; j++) {
                    results[m] += nums[j];
                }
                latch.countDown();
            });
        }
        double resultM3 = 0.0;
        long start = System.currentTimeMillis();
        for (Thread t : threads) {
            t.start();
        }
        latch.await();
        for (int i = 0; i < results.length; i++) {
            resultM3 += results[i];
        }
        long end = System.currentTimeMillis();
        System.out.println("m3: " + (end - start) + " result = " + df.format(result));
    }

 当前电脑cpu核数是32,我们把 threadCount 设置为32,发现只有2ms,说明跟CPU的核数是相关的。作为一台机器来说,32核,但是这台机器除了跑我们的程序之外,还有很多其他的线程,垃圾回收线程,帮我们分配资源的线程,这台机器除了java之外还有操作系统的线程,所以服务器本身跑程序的时候不一定只有你自己程序的线程,所以设置成CPU的核数也不一定能充分利用上,得看当前机器的运行情况,不能说让每个CPU全都利用100%,如果有其他线程执行的时候,都没有CPU去执行了,所以不能设置成CPU核数,所以出于安全的角度,我一般利用80%,如果有特别紧急的情况,还有20%的空间可以利用,未必32就是最合适的,要理解这一点.

实际工作当中,都过模拟,压测来找到一个比较合适的数.

 

 比如一个单核CPU的机器,一个程序有50%时间在做运算,50%的时间是在等待请求返回,那么2个线程就已经够了.

一个线程W/C,多长时间在等待,多长时间在计算呢?

工具 Profiler,性能分析工具有很多,比如java常用的J-profiler,但是这玩意TM的收费的,如果已经部署到服务器上了,跟压测的环境不一样了,可以用阿里的 arfhas,在这里只谈单机,分布式之间的服务调用,

posted @ 2022-11-17 08:17  宝宝佩恩天道  阅读(17)  评论(0编辑  收藏  举报