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<Runnable> 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<Runnable> 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();
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 本文出自 Eddy Wiki ,转载请注明出处:http://eddy.wiki/interview-java.h...
    eddy_wiki阅读 2,215评论 0 14
  • (一)Java部分 1、列举出JAVA中6个比较常用的包【天威诚信面试题】 【参考答案】 java.lang;ja...
    独云阅读 7,136评论 0 62
  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,341评论 11 349
  • 一、进程和线程 进程 进程就是一个执行中的程序实例,每个进程都有自己独立的一块内存空间,一个进程中可以有多个线程。...
    阿敏其人阅读 2,622评论 0 13
  • 【办护照】网上预约照相排队取号打印表格等待叫号按指纹(右大拇指、左大拇指)签字缴费200快递免费坑爹的是今天上午入...
    dq920813阅读 99评论 0 0