【Java并发编程】Java多线程(二):多线程一定好吗?线程阻塞时占用CPU吗?

1.多线程一定好吗?

1.1 多线程优势

1)阻塞等待时充分利用 CPU

当程序发生阻塞的操作时候,例如IO等待,CPU将就空闲下来了。而使用多线程,当一些线程发生阻塞的时候,另一些线程则仍能利用CPU,而不至于让CPU一直空闲。

2)利用 CPU 的多核并行计算能力

现在的CPU基本上都是多核的。使用多线程,可以利用多核同时执行多个线程,而不至于单线程时一个核心满载,而其他核心空闲。

1.2 多线程弊端

1)线程切换是有开销的,这会导致程序运行变慢。

2)多线程程序必须非常小心地同步代码,否则会引起死锁。

3)多线程程序极难调试,并且一些bug非常隐蔽,可能你99次运行都是对的,但是有1次是错的。不像单线程程序那么容易暴露问题。

1.3 代码测试

分别创建 1,10,100,1000,10000 个线程一共做100000000循环

public class CounterDemo {


    private static final long num = 1000000000L;
	
    // 执行循环,即每个线程的任务
    public static void splitCount(int threadNum) {
        // 通过num/threadNum计算每个线程要循环的次数,
        // 若一个线程则1000000000次,若 100000个线程则1000次
        for (long i = 0; i < num/threadNum; i++) {}
    }

    public static long getIntervalTimeToNow(long startTime) {
        return System.currentTimeMillis() - startTime;
    }

    public static void main(String[] args) throws InterruptedException {
        countWithMultithread(1); // 1个线程
        countWithMultithread(10); // 10个线程
        countWithMultithread(100); // 100个线程
        countWithMultithread(1000); // 1000个线程
        countWithMultithread(10000); // 10000个线程
    }

    private static void countWithMultithread(final int threadNum) throws InterruptedException {
        long startTime;
        Runnable splitCount = new Runnable() { 
            @Override
            public void run() {
                CounterDemo.splitCount(threadNum); // 每个线程需要做的事就是循环
            }
        };
        List<Thread> list = new ArrayList<>(); // 保存创建的线程们
        for (int i = 0; i < threadNum; i++) { // 创建threadNum个线程
            Thread thread1 = new Thread(splitCount); // 传入线程执行循环次数
            list.add(thread1);
        }
        startTime = System.currentTimeMillis(); // 开始时间
        for (Thread th: list) {
            th.start(); 
        }
        for (Thread th: list) {
            // 保证所有线程都在main之前运行完
            th.join();
        }
        System.out.println(String.format("%1$9d", threadNum) + " thread 
                           need:"+String.format("%1$6d",getIntervalTimeToNow(startTime)));
    
}
        1 thread need:   409
       10 thread need:    92
      100 thread need:   140
     1000 thread need:   226
    10000 thread need:   978  // 10000个线程反而比1个线程执行时间长
   100000 thread need: 10059  // 效率反而下降了

执行的时候,使用vmstat 查看 CS (context switch)切换次数

vmstat 1
r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
0  0      0 2701584 151472 9676072    0    0     0    60  393  419  0  0 100  0  0
0  0      0 2701708 151472 9676084    0    0     0     0  751  751  1  0 99  0  0
0  0      0 2701708 151472 9676092    0    0     0     0  354  384  0  0 100  0  0
0  0      0 2701708 151472 9676096    0    0     0     0  435  464  0  0 100  0  0
0  0      0 2701708 151472 9676108    0    0     0    72  439  491  0  0 100  0  0
2  0      0 2701708 151472 9676128    0    0     0    32  381  453  0  0 100  0  0
1  0      0 2680344 151472 9676168    0    0     0     4 6077 4869 21  1 78  0  0
4  0      0 2664740 151472 9676180    0    0     0     0 126656 149528 17 11 72  0  0
3  0      0 2564376 151472 9676188    0    0     0     0 107107 138418 12 11 77  0  0
3  0      0 2565556 151472 9676196    0    0     0     0 128143 166234  7 14 79  0  0
4  0      0 2563056 151472 9676204    0    0     0    64 125162 163707  7 13 79  0  0
3  0      0 2567808 151472 9676208    0    0     0     0 136266 180092  7 13 80  0  0
2  0      0 2566560 151472 9676216    0    0     0     0 117768 154666  7 14 79  0  0
1  0      0 2568276 151472 9676220    0    0     0     0 107585 139240  8 13 79  0  0

上下文切换最多的时候每秒切换了 18W+次:线程切换时需要保存当前线程的数据和函数栈

  • 当前任务的时间片用完之后,系统CPU正常调度下一个任务;
  • 当前任务碰到IO阻塞,调度线程将挂起此任务,继续下一个任务;
  • 多个任务抢占锁资源,当前任务没有抢到,被调度器挂起,继续下一个任务;
  • 用户代码挂起当前任务,让出CPU时间;
  • 硬件中断;

2.线程阻塞时占用CPU吗?

先说答案:阻塞不占用 CPU 时间,当某个线程阻塞时,该会触发 CPU 调度,即让新的线程在该 CPU 上运行(这也验证了上面说多的多线程的优势)。

2.1 CPU初始状态

2.2 线程运行

public static void main(String[] args) {
    for (int i = 0; i < 10000000; i++) {
        System.out.println("dfdf");
        System.out.println(456 + 567);
    }
}

可以看出cpu的利用率飙升到了65%,这时候 cpu 占用率已经很高了。

2.3 进入TIMED_WAITING 状态

public static void main(String[] args) {
    for (int i = 0; i < 10000000; i++) {
        try {
            Thread.sleep(100000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

下图可见 CPU 的利用率还是在 7% 左右,由此可以证明 sleep 方法并不会占用 CPU 的时间

2.4 进入WAITING 状态

public static void main(String[] args) {
    for (int i = 0; i < 10000000; i++) {
        Object o = new Object();
        synchronized (o) {
            try {
                o.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

下图可见 CPU 利用率是 6% ,由此可以证明wait方法也不会占用CPU的时间

猜你喜欢

转载自blog.csdn.net/qq_33762302/article/details/114342411