首页 > 编程语言 >【Java】guava(六)函数式编程与惰性求值

【Java】guava(六)函数式编程与惰性求值

时间:2022-11-11 12:40:27浏览次数:52  
标签:调用 Java get nanos Supplier 求值 return guava


举个例子,比如我们的web服务器应用,我们可能会写一个类似拦截器一样的模块来提前把一些公共参数抽取出来,比如像token,userid,ip地址等等这样的信息,放入一个类似ThreadLocal的对象中,后面的controller如果想要用就可以直接拿。

方案一:及早求值,每一次都直接计算出最终结果,存放至ThreadLocal;

实现起来很直观也很简单,但是可能大多数请求在被后面的controller处理时,只会使用其中的一个或几个,甚至一个都不用,这样的话,每一个都计算就会很浪费资源了。

方案二:惰性求值,在ThreadLocal处只存储求值方法,但是不执行求值过程,只有在真正使用时才执行求值过程。这个方案比上面的好多了,起码避免了大量的无用计算。但是可能有一个问题,如果我需要多次使用其中的变量,进行了多次调用,那么就会计算多次,好像是重复计算。确实有这个问题,但是如果值在整个请求生命周期中不变,比如像userId,ip这种的,其实可以调用一次,然后存入一个变量,后面直接使用这个变量即可。

Guava的函数式编程Supplier对上述惰性求值有很好的支持,下面看下:

先看下guava的Supplier接口:

@GwtCompatible
@FunctionalInterface
public interface Supplier<T> extends java.util.function.Supplier<T> {
/**
* Retrieves an instance of the appropriate type. The returned object may or may not be a new
* instance, depending on the implementation.
*
* @return an instance of the appropriate type
*/
@CanIgnoreReturnValue
@Override
T get();
}

我们求值的方法需要被封在这个接口的get方法里。

例子:

@Test
public void testD() throws InterruptedException {
Supplier<Integer> id1 = Suppliers.memoize(() -> fetch());
Supplier<Integer> id2 = Suppliers.memoizeWithExpiration(() -> fetch(), 1, TimeUnit.SECONDS);
System.out.println("****************");
for (int i = 0 ; i < 10; i ++) {
System.out.println(id1.get());
System.out.println(id2.get());
System.out.println("****************");
Thread.sleep(1000);
}
}

private int fetch(){
return new Random().nextInt(10);
}

****************
4
5
****************
4
9
****************
4
3
****************
4
3
****************
4
2
****************
4
8
****************
4
1
****************
4
0
****************
4
3
****************
4
4
****************

Process finished with exit code 0

将我们的求值方法封装在一个Supplier接口内,可以直接new一个Supplier。这种方式是最原始的,每一次get都会调用一次求值。对于很简单的求值计算,似乎这种已经足够了。

但是guava为我们提供了另外两种更高级的用法。

一个是memoize方法,内部会保证求值方法只在第一次get时被调用,然后结果被存下来,后面的get直接返回结果即可。

当然,如果求值过程需要更新怎么办?

guava提供了另一个memoizeWithExpiration方法, 会根据时间间隔适时调用求值方法更新本地缓存的结果。

上面的例子很清楚。

看下实现:

对于memoize,其内部方法是这样实现的:

public T get() {
// A 2-field variant of Double Checked Locking.
if (!initialized) {
synchronized (this) {
if (!initialized) {
T t = delegate.get();
value = t;
initialized = true;
// Release the delegate to GC.
delegate = null;
return t;
}
}
}
return value;
}

有点类似单例模式,使用了一个同步操作判定是否初始化了。

对于memoizeWithExpiration,其内部方法是这样实现的:

public T get() {
// Another variant of Double Checked Locking.
//
// We use two volatile reads. We could reduce this to one by
// putting our fields into a holder class, but (at least on x86)
// the extra memory consumption and indirection are more
// expensive than the extra volatile reads.
long nanos = expirationNanos;
long now = Platform.systemNanoTime();
if (nanos == 0 || now - nanos >= 0) {
synchronized (this) {
if (nanos == expirationNanos) { // recheck for lost race
T t = delegate.get();
value = t;
nanos = now + durationNanos;
// In the very unlikely event that nanos is 0, set it to 1;
// no one will notice 1 ns of tardiness.
expirationNanos = (nanos == 0) ? 1 : nanos;
return t;
}
}
}
return value;
}

存了一个下一次必须调用求值方法的时间戳,然后每一次调用都比较一下看是否需要求值。存时间戳也是一个常见的实现周期性调用的策略。

标签:调用,Java,get,nanos,Supplier,求值,return,guava
From: https://blog.51cto.com/u_15873544/5844581

相关文章

  • 【Java】okHttp3 简单使用
    之前用的RestTemplate,发现一个multipart的http请求始终发不成功,后面就试了下okHttp,发现真的好用,api太清爽了,记录一下使用:packagecom.liyao;importjava.io.FileInputStream......
  • 【Grpc(一)】Java 何如理解StreamObserver?
    刚开始接触Grpc时,桩代码里有许多StreamObserver类型,不太清楚是怎么用的,这里做一个记录。首先看下StreamObserver接口定义:publicinterfaceStreamObserver<V>{voidon......
  • 【Java】随机数原理 Random ThreadLocalRandom
    大致生成原理:随机数由seed经过一定的转换生成。需要提供初始seed。每一次生成随机数时,先由老seed生成新seed,再根据新seed生成新的随机数。由于算法是固定的,所以如果初始seed......
  • 【docker】Java应用 容器内存管理 -XX:+UseContainerSupport
    早期时候,容器内运行Java应用程序时,Jvm无法感知容器环境存在,所以对容器资源的限制比如内存或者cpu等都无法生效。原因是容器的资源管理使用了操作系统cgroup机制,但是Jvm无法......
  • 【Java】Instrumentation热更新 premain agentmain
    有两种办法:1)在java5中,可以利用jvm加载类的一个扩展点来实现类文件的动态修改。需要提供一个premain方法。缺点是只能在类文件加载且main方法执行之前修改,无法实现真正的运行......
  • 【zookeeper】java API 例子
    之前体验了命令行客户端,这次看一下javaAPI操作zk。server还是按照之前的配置,一个server1,server2和server3的伪集群。maven:这里使用maven管理zk的jar包,大致需要zk的jar和日......
  • 狂神说Javase基础学习1
    狂神学习博客1基本的DOS命令打开CMD的方式1.开始+系统+命令提示符2.win+R,进入运行窗口,输入cmd打开控制台3.在任意的文件夹下面,Shift+鼠标右键,进入命令行窗口4.资源管......
  • 【Java】内存模型 volatile
    java堆存储对象和数组,是一块线程共享数据区,但是实际线程运行的时候,对于用到的对象都会在线程私有空间即虚拟机栈保存一个副本,为了效率。这两快内存叫主内存和工作内存。java......
  • 【Java】内存区域与对象创建
    这块内容是java很基础的部分,涉及到JVM的设计原理,很久以前就看到过,这次需要区分线程私有和共享基本java的运行时数据区可以分为五大块:程序计数器,为线程私有,每一个线程都有一......
  • 【Java】split(
    java的split函数接受一个正则表达式的分隔符为参数,将string按照分隔符划分为一个数组。我们可能会忽略这个参数的要求,这里传入的分隔符并不是一个普通的字符串,而是一个正则......