写了多年代码,你却不知道的程序设计的5个底层逻辑( 七 )


为了让应用程序免于数据竞争的干扰, Java 内存模型中定义了 happen-before 来描述两个操作的内存可见性,也就是 X 操作 happen-before 操作 Y , 那么 X 操作结果 对 Y 可见 。
JVM 中针对 volatile 以及 锁 的实现有 happen-before 规则, JVM 底层通过插入内存屏障来限制编译器的重排序,以 volatile 为例,内存屏障将不允许 在 volatile 字段写操作之前的语句被重排序到写操作后面 , 也不允许读取 volatile 字段之后的语句被重排序带读取语句之前 。插入内存屏障的指令,会根据指令类型不同有不同的效果,例如在 monitorexit 释放锁后会强制刷新缓存,而 volatile 对应的内存屏障会在每次写入后强制刷新到主存,并且由于 volatile 字段的特性,编译器无法将其分配到寄存器,所以每次都是从主存读取,所以 volatile 适用于读多写少得场景,最好只有个线程写多个线程读,如果频繁写入导致不停刷新缓存会影响性能 。
关于应用程序中设置多少线程数合适的问题,我们一般的做法是设置 CPU 最大核心数 * 2 ,我们编码的时候可能不确定运行在什么样的硬件环境中,可以通过 Runtime.getRuntime().availableProcessors() 获取 CPU 核心 。
但是具体设置多少线程数,主要和线程内运行的任务中的阻塞时间有关系,如果任务中全部是计算密集型,那么只需要设置 CPU 核心数的线程就可以达到 CPU 利用率最高,如果设置的太大,反而因为线程上下文切换影响性能,如果任务中有阻塞操作,而在阻塞的时间就可以让 CPU 去执行其他线程里的任务,我们可以通过 线程数量=内核数量 / (1 - 阻塞率)这个公式去计算最合适的线程数,阻塞率我们可以通过计算任务总的执行时间和阻塞的时间获得 。
目前微服务架构下有大量的RPC调用,所以利用多线程可以大大提高执行效率,我们可以借助分布式链路监控来统计RPC调用所消耗的时间,而这部分时间就是任务中阻塞的时间,当然为了做到极致的效率最大,我们需要设置不同的值然后进行测试 。

写了多年代码,你却不知道的程序设计的5个底层逻辑

文章插图
 
Java 中如何实现定时任务定时器已经是现代软件中不可缺少的一部分,例如每隔5秒去查询一下状态,是否有新邮件,实现一个闹钟等, Java 中已经有现成的 api 供使用,但是如果你想设计更高效,更精准的定时器任务,就需要了解底层的硬件知识,比如实现一个分布式任务调度中间件,你可能要考虑到各个应用间时钟同步的问题 。
Java 中我们要实现定时任务,有两种方式,一种通过 timer 类, 另外一种是 JUC 中的 ScheduledExecutorService ,不知道大家有没有好奇 JVM 是如何实现定时任务的,难道一直轮询时间,看是否时间到了,如果到了就调用对应的处理任务,但是这种一直轮询不释放 CPU 肯定是不可取的,要么就是线程阻塞,等到时间到了在来唤醒线程,那么 JVM 怎么知道时间到了,如何唤醒呢?
首先我们翻一下 JDK ,发现和时间相关的 API 大概有3处,而且这 3 处还都对时间的精度做了区分:
object.wait(long millisecond) 参数是毫秒,必须大于等于 0 ,如果等于 0 ,就一直阻塞直到其他线程来唤醒 ,timer 类就是通过 wait() 方法来实现,下面我们看一下wait的另外一个方法:
public final void wait(long timeout, int nanos) throws InterruptedException { if (timeout < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos > 0) { timeout++; } wait(timeout); }这个方法是想提供一个可以支持纳秒级的超时时间,然而只是粗暴的加 1 毫秒 。
Thread.sleep(long millisecond) 目前一般通过这种方式释放 CPU ,如果参数为 0 ,表示释放 CPU 给更高优先级的线程,自己从运行状态转换为可运行态等待 CPU 调度,他也提供了一个可以支持纳秒级的方法实现,跟 wait 额区别是它通过 500000 来分隔是否要加 1 毫秒 。
public static void sleep(long millis, int nanos) throws InterruptedException { if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } if (nanos < 0 || nanos > 999999) { throw new IllegalArgumentException( "nanosecond timeout value out of range"); } if (nanos >= 500000 || (nanos != 0 && millis == 0)) { millis++; } sleep(millis); }LockSupport.park(long nans) Condition.await()调用的该方法, ScheduledExecutorService 用的 condition.await() 来实现阻塞一定的超时时间,其他带超时参数的方法也都通过他来实现,目前大多定时器都是通过这个方法来实现的,该方法也提供了一个布尔值来确定时间的精度 。


推荐阅读