Java while循环cpu占用高排查和优化

Java 自带性能分析工具

命令行工具的功能都很强大,像jmap、jstat、jstack、jps这些,功能和一些收费软件差不多,但是没有GUI看起来就有些费劲。由于使用Windows分析就使用自带的Jmc来用了,只要在命令行输入jmc就可以启动。


jmc示例图

cpu占用排查

由于已经知道cpu占用原因,这里就直接给出错误的示例。如果在服务器上排查需要结合上述命令行工具,具体使用可以参考这里。

  • 错误示例
public class TestCpu {
    public static void main(String[] args) throws InterruptedException {
        TaskThread thread = new TaskThread();
        thread.setName("Test Cpu");
        thread.start();

        // 为了让程序不退出,这里休眠一个较长时间
        // sleep 在执行后基本不产生性能损耗
        Thread.sleep(1000000);
    }
}

class TaskThread extends Thread {

    // 由于while循环占用锁导致变量无法观测到变化,可以使用volatile解决,也可以使用并发包下的集合解决
    private volatile Queue tasks = new ArrayDeque<>();

    public synchronized void add(Runnable runnable) {
        tasks.add(runnable);
    }

    @Override
    public void run() {
        while (true) {
            while (!tasks.isEmpty()) {
                Runnable runnable = tasks.remove();
                runnable.run();
            }
        }
    }
}


上述代码在运行又占用cpu稳定在15%左右,机器配置为I7 4核8G

  • 使用jcm分析性能消耗
    先使用飞行记录器记录一段时间的运行情况

    飞行记录器

    线程

    可以看到测试线程基本占用了所有资源(实际服务器上业务逻辑相对复杂,占有率一般不会占这么高)。
    但是代码实际只是判断了一个集合是否为空,为什么CPU占用会这么高呢?原因可以参考这里
    简单解释就是在Windows上如果一个进程得到了 CPU 时间,除非它自己放弃使用 CPU ,否则将完全霸占 CPU。在Linux上相对好一点,因为时基于时间片算法的。
    在知道原因后就比较好办了,解决方法无非就是在不需要执行无谓的循环的时把CPU释放出来。最简单的做法就是在while循环种调用Thread.sleep方法释放CPU的占用。但这样存在频繁的切换线程调度的问题。

  • 使用wait和notify阻塞和唤醒线程
    wait和notify的原理可以参考这里。对TaskThread修改代码如下,配合这2个方法可以保证在线程空闲时处于一个挂起状态,CPU占用在空闲时也基本为0了。

class TaskThread extends Thread {

    private final Deque tasks = new ArrayDeque<>();

    public void add(Runnable runnable) {
        synchronized (tasks) {
            tasks.add(runnable);
            if (tasks.pollFirst() == runnable) {
                tasks.notify();
            }
        }
    }

    @Override
    public void run() {
        try {
            while (true) {
                synchronized (tasks) {
                    if (tasks.isEmpty()) {
                        tasks.wait();
                    }
                }
                while (!tasks.isEmpty()) {
                    Runnable runnable = tasks.remove();
                    runnable.run();
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

你可能感兴趣的:(Java while循环cpu占用高排查和优化)