首页 > 编程知识 正文

time wait过多(system类在什么包中)

时间:2023-05-05 16:20:52 阅读:75988 作者:1601

http://www.Sina.com/http://www.Sina.com /

资料来源: juejin.cn/post/6887743425437925383

我很怀疑,System.currentTimeMillis真的有性能问题吗? 最近,在研究中间件源代码时,我们发现获取当前时间的不是System.currentTimeMillis,而是定制的System.currentTimeMillis缓存类见下面。 System.currentTimeMillis的性能这么差吗? 居然用自定义的高速缓存时钟代替?

/**是弱精度的计时器,考虑到性能,不使用同步策略。 * @ author mycat */publicclasstimeutil (/当前毫秒的缓存privatestaticvolatilongcurrent _ time=system.current time millis () publicstaticfinallongcurrenttimemillis (({ return current _ time; } publicstaticfinallongcurrenttimenanos () {returnSystem.nanoTime; //缓存publicstaticfinalvoidupdate ({ current _ time=system.current time millis ); //使用调度任务调度线程池,并定期调用update方法(每1s )以获取缓存时钟heart beat scheduler.scheduleatfixedrate (处理器) 为了跟上更新timeunit.mmode的时代潮流,跟上性能优化的“大师”们的步伐,我赶紧在网上搜索“currentTimeMillis性能”。 结果,10个搜索结果中有9个涉及system.currentTimeMillis性能问题:

单击以查看,如果System.currentTimeMillis比new上的普通对象花了大约100倍的时间,并且同时调用System.currentTimeMillis时,它还会提供测试记录

我希望System.currentTimeMillis有什么性能问题看到这里,我马上打开IDEA,更换代码中的所有System.currentTimeMillis,但作为严格的程序员,我希望所以我仔细拜读了这些文章,总结了他们的观点:

System.currentTimeMillis访问系统时钟。 这是关键节的资源,同时也可能导致多线程冲突

System.currentTimeMillis ()之所以慢,是因为我去和系统谈过一次话

有测试记录。 需要250倍于单线程的时间。

但我仔细一看,发现这些观点充满了漏洞:

1.System.currentTimeMillis确实必须访问系统时钟。 准确地说,读取墙壁的时间(xtime )。 xtime是Linux系统用于在用户空间中获取当前时间的,内核本身很少使用,只是维护更新。 此外,读写xtime使用Linux内核中的顺序锁,而不是互斥锁,并且不影响读线程之间的关系

大家可以把顺序锁定看成是解决了“ABA问题”的CompareAndSwap锁定。 关键安全资源(此处为xtime )具有操作序列号,写入操作使序列号为1,无法进行读取操作。

写入: CAS将序列号1

读取操作:获取序列号,读取数据,然后再次获取序列号。 如果前后两次获取的序列号相同,则证明读取操作时没有写入操作的干扰,这次读取有效,返回数据。 否则,数据可能在读取时发生了更改,此次读取无效,将重新进行读取操作。

大家可能有疑问。 阅读xtime时数据是否可能发生更改? 难读的操作不是原子性的吗? 这是因为,xtime为64位,在32位机器上需要分两次读取,但在64位机器上不会产生该同时性的问题。

2 .与系统进行了一次交流。 确实,用户进程必须处于内核状态才能访问系统资源。 但是,new的一个对象,分配内存也是系统调用,必须进入内核状态与系统进行交互。 光是阅读系统墙壁上的时间,不是就比移动内存指针或初始化内存要花100倍的时间吗? 觉得不可思议

3 .关于所谓的测试记录,请给我看他的测试代码:

此测试代码的问题在于,锁定endLatch.countDown的时间总体上也需要时间。 锁定是基于CAS实现的,在目前这种计算密集型场景中,大量线程蜂拥而至时,几乎都会因CAS失败而锁定,大量线程锁定、排队和放弃的时间不少。

然后,使用此方法(从运行开始到运行完成)比较并发和单线程调用时间也是一个问题。 单线程如何与多线程比较总的执行时间? 我认为被比较的是每次呼叫的时间之和。 (见下文) )

longbegin=System.nanoTime (; //system.currenttimemil一次调用

lis()long end = System.nanoTime();sum += end - begin;

记录每次调用的总耗时,这种方法虽然会把System.nanoTime()也算进总耗时里,但因为不论并发测试还是单线程测试都会记录System.nanoTime(),不会导致测试的不公平

数据说话,System.currentTimeMillis的性能没有问题

通过改进测试代码(测试代码见文末),并添加了优化“大师”们的缓存时钟做对比,我得到了以下数据:

System代表 System.currentTimeMillis

缓存时钟代表 使用静态成员变量做System.currentTimeMillis缓存的时钟类

200线程-Tomcat的默认线程数

使用JMH(Java基准测试框架)的测试结果

JMH按照推荐使用了双倍CPU的线程数(8线程),统计的是平均时间,测试代码见文末 另外Windos和Linux配置不同,彼此间无可比性

测试结果分析

可以看到System.currentTimeMillis并发性能并不算差,在次数较少(短期并发调用)的情况下甚至比单线程要强很多,而在单线程调用时效率也要比缓存时钟要高一倍左右。实际环境中几乎是达不到上述测试中的多线程长时间并发调用System.currentTimeMillis这样的情况的,因而我认为没有必要对System.currentTimeMillis做所谓的“优化”

这里没有做“new一个对象”的测试,是因为并不是代码里写了new Object(),JVM就会真的会给你在堆内存里new一个对象。

这是JVM的一个编译优化——逃逸分析:先分析要创建的对象的作用域,如果这个对象只在一个method里有效(局部变量对象),则属于未 方法逃逸,不去实际创建对象,而是你在method里调了对象的哪个方法,就把这个方法的代码块内联进来。只在线程内有效则属于未 线程逃逸,会创建对象,但会自动消除我们做的无用的同步措施。

最后

纸上得来终觉浅,绝知此事要躬行

想要学习JMH,请跟着GitHub官方文档走,别人的博客可能跑不通就搬上去了,笔者也是刚刚踩过了这个坑

最后奉上我的测试代码

测试代码: public class CurrentTimeMillisTest {    public static void main(String[] args) {        int num = 10000000;        System.out.print("单线程"+num+"次System.currentTimeMillis调用总耗时:    ");        System.out.println(singleThreadTest(() -> {            long l = System.currentTimeMillis();        },num));        System.out.print("单线程"+num+"次CacheClock.currentTimeMillis调用总耗时:");        System.out.println(singleThreadTest(() -> {            long l = CacheClock.currentTimeMillis();        },num));        System.out.print("并发"+num+"次System.currentTimeMillis调用总耗时:      ");        System.out.println(concurrentTest(() -> {            long l = System.currentTimeMillis();        },num));        System.out.print("并发"+num+"次CacheClock.currentTimeMillis调用总耗时:  ");        System.out.println(concurrentTest(() -> {            long l = CacheClock.currentTimeMillis();        },num));    }    /**     * 单线程测试     * @return     */    private static long singleThreadTest(Runnable runnable,int num) {        long sum = 0;        for (int i = 0; i < num; i++) {            long begin = System.nanoTime();            runnable.run();            long end = System.nanoTime();            sum += end - begin;        }        return sum;    }    /**     * 并发测试     * @return     */    private static long concurrentTest(Runnable runnable,int num) {        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(200,200,60, TimeUnit.SECONDS,new LinkedBlockingQueue<>(num));        long[] sum = new long[]{0};        //闭锁基于CAS实现,并不适合当前的计算密集型场景,可能导致等待时间较长        CountDownLatch countDownLatch = new CountDownLatch(num);        for (int i = 0; i < num; i++) {            threadPoolExecutor.submit(() -> {                long begin = System.nanoTime();                runnable.run();                long end = System.nanoTime();                //计算复杂型场景更适合使用悲观锁                synchronized(CurrentTimeMillisTest.class) {                    sum[0] += end - begin;                }                countDownLatch.countDown();            });        }        try {            countDownLatch.await();        } catch (InterruptedException e) {            e.printStackTrace();        }        return sum[0];    }    /**     * 缓存时钟,缓存System.currentTimeMillis()的值,每隔20ms更新一次     */    public static class CacheClock{        //定时任务调度线程池        private static ScheduledExecutorService timer = new ScheduledThreadPoolExecutor(1);        //毫秒缓存        private static volatile long timeMilis;              static {            //每秒更新毫秒缓存            timer.scheduleAtFixedRate(new Runnable() {                @Override                public void run() {                    timeMilis = System.currentTimeMillis();                }            },0,1000,TimeUnit.MILLISECONDS);        }                public static long currentTimeMillis() {            return timeMilis;        }    }} 使用JMH的测试代码: @BenchmarkMode(Mode.AverageTime)@OutputTimeUnit(TimeUnit.MICROSECONDS)//120轮预热,充分利用JIT的编译优化技术@Warmup(iterations = 120,time = 1,timeUnit = TimeUnit.MILLISECONDS)@Measurement(time = 1,timeUnit = TimeUnit.MICROSECONDS)//线程数:CPU*2(计算复杂型,也有CPU+1的说法)@Threads(8)@Fork(1)@State(Scope.Benchmark)public class JMHTest {    public static void main(String[] args) throws RunnerException {        testNTime(10000);    }    private static void testNTime(int num) throws RunnerException {        Options options = new OptionsBuilder()                .include(JMHTest.class.getSimpleName())                .measurementIterations(num)                .output("E://testRecord.log")                .build();        new Runner(options).run();    }    /**     * System.currentMillisTime测试     * @return 将结果返回是为了防止死码消除(编译器将 无引用的变量 当成无用代码优化掉)     */    @Benchmark    public long testSystem() {        return System.currentTimeMillis();    }    /**     * 缓存时钟测试     * @return     */    @Benchmark    public long testCacheClock() {        return JMHTest.CacheClock.currentTimeMillis();    }    /**     * 缓存时钟,缓存System.currentTimeMillis()的值,每隔1s更新一次     */    public static class CacheClock{        private static ScheduledExecutorService timer = new ScheduledThreadPoolExecutor(1);        private static volatile long timeMilis;        static {            timer.scheduleAtFixedRate(new Runnable() {                @Override                public void run() {                    timeMilis = System.currentTimeMillis();                }            },0,1000,TimeUnit.MILLISECONDS);        }        public static long currentTimeMillis() {            return timeMilis;        }    }}

●【练手项目】基于SpringBoot的ERP系统,自带进销存+财务+生产功能

●分享一套基于SpringBoot和Vue的企业级中后台开源项目,代码很规范!

●能挣钱的,开源 SpringBoot 商城系统,功能超全,超漂亮!

PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持我们吧!

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。