提高工作效率的利器

目录

一 Future 多线程处理任务

1.1 定义线程池

1.2 Future 实践应用

二 内存分页

2.1 分页代码

2.2 执行结果

三 Java8 stream 实现多字段排序

3.1 代码实现

3.2 运行结果

四 Search Scroll API 

4.1 Scroll API 代码

4.2 Scroll API 流程梳理


本篇文章主要是为了记录工作中经常使用的一些工具,以及解决常见问题的一些常用套路。

一 Future 多线程处理任务

你是否遇到过这种场景,有大量数据需要进行处理,比如几百万、几千万,甚至上亿的数据?

如果使用单线程来处理大量的数据,耗时将会很长,效率太低。你也许会说,这简单啊,用多线程啊。是的,多线程相当于多个单线程同时处理,确实快很多。

那么如何使用多线程来处理?

你会说用线程池哈。哈哈,线程池确实很常用,使用线程池确实能达到预期效果。

如果需要获取线程处理得到的结果,又该如何呢?

Runnable 接口,它的方法没有返回值,不符合要求。
Callable 接口,和 Runnable 接口比,多了一个返回值,符合要求。

1.1 定义线程池

线程池原理请看这篇文章深入理解和运用线程池

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 
@Configuration
public class TaskThreadPoolConfig {
 
    private int corePoolSize = 8;
    private int maxPoolSize = 8;
    private int queueCapacity = 1000;
 
    @Bean(name = "taskExecutor")
    public ThreadPoolTaskExecutor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(corePoolSize);
        executor.setMaxPoolSize(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        return executor;
    }
}

1.2 Future 实践应用

import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.IntStream;
import org.springframework.stereotype.Service;
 
@Service
public class TaskExecutor {
    @Resource(name = "taskExecutor")
    ThreadPoolTaskExecutor threadPoolTaskExecutor;
 
    public List<Integer> futureList() throws ExecutionException, InterruptedException {
        //初始化任务数组
        List<Integer> initList = new ArrayList<>();
        IntStream.range(0, 30).forEach(initList::add);
 
        List<Future> futureList = new ArrayList<>();
        //多线程执行
        initList.forEach(id -> {
            Future<Integer> future = threadPoolTaskExecutor.submit(() -> getUserId(id));
            //将future写入list
            futureList.add(future);
        });
        
        //获取多线程执行结果
        List<Integer> resultList = new ArrayList<>();
        for (Future<Integer> future : futureList) {
            resultList.add(future.get());
        }
        
        return resultList;
    }
    
    public Integer getUserId(Integer i) {
        return i;
    }
}

从上面的例子可以看到:

1 当我们提交一个 Callable 任务后,我们会同时获得一个 Future 对象,

2 主线程某个时刻调用 Future 对象的 get() 方法,就可以获得异步执行的结果。

3 在调用 get()时,如果异步任务已经完成,我们就直接获得结果;如果异步任务还没有完成,那么 get()会阻塞,直到任务完成后才返回结果。

综上所述:我们可以看到,使用 Future 对象的 get() 方法,可能会阻塞,系统的吞吐率很难提高。CompletableFuture 是 Future 的升级版,这里不做深入研究。

二 内存分页

在实际工作中,经常会遇到这样一种场景,需要展示列表,出于对系统性能的考虑,需要对列表进行分页渲染。

2.1 分页代码

import org.apache.commons.lang3.tuple.Pair;

public static void main(String[] args) {
    List<Integer> list =  Arrays.asList(1, 2, 3, 4, 5, 6);
    Pair<Integer, List<Integer>> pair = page(list, 0, 2);
    System.out.println(pair.getLeft());
    System.out.println(pair.getRight());
}

public static Pair<Integer, List<Integer>> page(List<Integer> list, Integer offset, Integer limit) {
    // offset偏移量与total比较
    int total = CollectionUtils.isNotEmpty(list) ? list.size() : 0;
    if (offset > total) {
        return Pair.of(total, Lists.newArrayList());
    }

    // 计算开始偏移量和最终偏移量
    int end = offset + limit;
    // 最终偏移量不能大于total
    end = Math.min(end, total);
    // 分页取子集
    List<Integer> result = list.subList(offset, end);
    return Pair.of(total, result);
}

2.2 执行结果

三 Java8 stream 实现多字段排序

工作中经常会遇到对字段进行排序的操作,如果不是需要单独定义排序规则的话,可以使用 stream 提供的排序 API,支持多字段排序,简单易用。

3.1 代码实现

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {
    private Long id;
    private String name;
    private Integer age;

    public static void main(String[] args) {
        List<User> userList = new ArrayList<>();
        userList.add(User.builder().id(10L).name("zs").age(11).build());
        userList.add(User.builder().id(10L).name("zs").age(10).build());
        userList.add(User.builder().id(5L).name("ls").age(9).build());
        userList.add(User.builder().id(5L).name("mz").age(9).build());
        userList.add(User.builder().id(19L).name("ww").age(10).build());
        System.out.println("排序之前:");
        for (User u:userList) {
            System.out.println(u);
        }

        // 假设我们以id降序、name降序、age升序对数组进行排序
        List<User> sort = userList.stream().sorted(Comparator.comparing(User::getId, Comparator.reverseOrder())
                .thenComparing(User::getName, Comparator.reverseOrder())
                .thenComparing(User::getAge, Comparator.naturalOrder())).collect(Collectors.toList());
        System.out.println("排序之后:");
        for (User u : sort) {
            System.out.println(u);
        }
    }
}

3.2 运行结果

四 Search Scroll API 

主要解决从索引里面取大数据量的场景,Scroll API 分批取数据的方式提高了查询效率。

4.1 Scroll API 代码

// 设置索引
SearchRequest searchRequest = new SearchRequest("posts");

// 设置scroll生命周期
final Scroll scroll = new Scroll(TimeValue.timeValueMinutes(1L));
searchRequest.scroll(scroll);

// 设置搜索条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(matchQuery("title", "Elasticsearch"));
// 设置单次查询数目
searchSourceBuilder.size(size); 
searchRequest.source(searchSourceBuilder);


// 第一次查询获取结果
SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); 
String scrollId = searchResponse.getScrollId();
SearchHit[] searchHits = searchResponse.getHits().getHits();

while (searchHits != null && searchHits.length > 0) { 
    // 构造scroll请求,查询下一页
    SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId); 
    scrollRequest.scroll(scroll);
    searchResponse = client.scroll(scrollRequest, RequestOptions.DEFAULT);
    scrollId = searchResponse.getScrollId();
    searchHits = searchResponse.getHits().getHits();
}

// 从上下文清除数据
ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); 
clearScrollRequest.addScrollId(scrollId);
ClearScrollResponse clearScrollResponse = client.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
boolean succeeded = clearScrollResponse.isSucceeded();

4.2 Scroll API 流程梳理

1. 通过发送初始 SearchRequest 初始化搜索上下文。

2. 通过在循环中调用 Search Scroll api 检索所有搜索命中,直到不返回任何文档。

3. 处理返回的搜索结果。

4. 创建一个新的 SearchScrollRequest,其中包含最后返回的滚动标识符和滚动间隔。

5. 滚动完成后清除滚动上下文。

参考文档:Scroll API 官方文档

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值