BlockingQueue

引言

BlockingQueue 的功能以及常见使用场景是非常广泛的,读者可以自行百度去了解 BlockingQueue的核心方法以及BlockingQueue家庭大致有哪些成员,这里就不再班门弄斧。推荐资料:不怕难之BlockingQueue及其实现

案例一:实现web聊天功能

这里引用一个案例基于BlockingQueue实现Web中的长连接聊天功能

BlockingQueueTest

package com.baba.bracelet;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
/**
 * @Author wulongbo
 * @Date 2020/12/1 9:16
 * @Version 1.0
 */
 public class BlockingQueueTest {
    public static void main(String[] args) throws InterruptedException {
        // 声明一个容量为10的缓存队列
 BlockingQueue queue = new LinkedBlockingQueue(10);
 //new了三个生产者和一个消费者
 Producer producer1 = new Producer(queue);
 Producer producer2 = new Producer(queue);
 Producer producer3 = new Producer(queue);
 Consumer consumer = new Consumer(queue);
 // 借助Executors
 ExecutorService service = Executors.newCachedThreadPool();
 // 启动线程
 service.execute(producer1);
 service.execute(producer2);
 service.execute(producer3);
 service.execute(consumer);
 // 执行10s
 Thread.sleep(10 * 1000);
 producer1.stop();
 producer2.stop();
 producer3.stop();
 Thread.sleep(2000);
 // 退出Executor
 service.shutdown();
 }
}

Producer生产者

package com.spring.security.demo;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
 * @Author wulongbo
 * @Date 2020/12/1 9:17
 * @Version 1.0
 */
 public class Producer implements Runnable {
    private volatile boolean isRunning = true;//是否在运行标志
 private BlockingQueue queue;//阻塞队列
 private static AtomicInteger count = new AtomicInteger();//自动更新的值
 private static final int DEFAULT_RANGE_FOR_SLEEP = 1000;
 //构造函数
 public Producer(BlockingQueue queue) {
        this.queue = queue;
 }
    public void run() {
        String data = null;
 Random r = new Random();
 System.out.println("启动生产者线程!");
 try {
            while (isRunning) {
                System.out.println("正在生产数据...");
 Thread.sleep(r.nextInt(DEFAULT_RANGE_FOR_SLEEP));//取0~DEFAULT_RANGE_FOR_SLEEP值的一个随机数
 data = "data:" + count.incrementAndGet();//以原子方式将count当前值加1
 System.out.println("将数据:" + data + "放入队列...");
 if (!queue.offer(data, 2, TimeUnit.SECONDS)) {//设定的等待时间为2s,如果超过2s还没加进去返回true
 System.out.println("放入数据失败:" + data);
 }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
 Thread.currentThread().interrupt();
 } finally {
            System.out.println("退出生产者线程!");
 }
    }
    public void stop() {
        isRunning = false;
 }
}

Consumer消费者

package com.spring.security.demo;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
/**
 * @Author wulongbo
 * @Date 2020/12/1 9:21
 * @Version 1.0
 */
 public class Consumer implements Runnable {
    private BlockingQueue queue;
 private static final int DEFAULT_RANGE_FOR_SLEEP = 1000;
 //构造函数
 public Consumer(BlockingQueue queue) {
        this.queue = queue;
 }
    public void run() {
        System.out.println("启动消费者线程!");
 Random r = new Random();
 boolean isRunning = true;
 try {
            while (isRunning) {
                System.out.println("正从队列获取数据...");
 String data = queue.poll(2, TimeUnit.SECONDS);//有数据时直接从队列的队首取走,无数据时阻塞,在2s内有数据,取走,超过2s还没数据,返回失败
 if (null != data) {
                    System.out.println("拿到数据:" + data);
 System.out.println("正在消费数据:" + data);
 Thread.sleep(r.nextInt(DEFAULT_RANGE_FOR_SLEEP));
 } else {
                    // 超过2s还没数据,认为所有生产线程都已经退出,自动退出消费线程。
 isRunning = false;
 }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
 Thread.currentThread().interrupt();
 } finally {
            System.out.println("退出消费者线程!");
 }
    }
}

以上的简单代码演示了如何使用 BlockingQueue以及它的部分核心方法的使用,读者能够很好举一反三。

案例二:批量插入mysql

该案例基于springboot集成Mybatis-Plus的项目中来说明:读者可以参考搭建Springboot项目并集成Mybatis-Plus javaspringboot
现在来做前期的环境准备。
这里为了方便测试我们加入swagger依赖:




 io.springfox
 springfox-swagger2
 2.9.2


 io.springfox
 springfox-swagger-ui
 2.9.2

config目录中添加swagger配置类
SwaggerConfig

package com.mybatis.plus.config;
import freemarker.ext.util.ModelCache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Parameter;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
import java.util.List;
/**
 * @Author wulongbo
 * @Date 2020/11/21 11:50
 * @Version 1.0
 */
@Configuration
@EnableSwagger2
public class SwaggerConfig {
    /**
 * 通过 createRestApi函数来构建一个DocketBean
 * 函数名,可以随意命名,喜欢什么命名就什么命名
 */
 @Bean
 public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())//调用apiInfo方法,创建一个ApiInfo实例,里面是展示在文档页面信息内容
 .select()
                //控制暴露出去的路径下的实例
 //如果某个接口不想暴露,可以使用以下注解
 //@ApiIgnore 这样,该接口就不会暴露在 swagger2 的页面下
 .apis(RequestHandlerSelectors.basePackage("com.mybatis.plus"))
                .paths(PathSelectors.any())
                .build();
 }
    //构建 api文档的详细信息函数
 private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                //页面标题
 .title("Spring Boot Swagger2 构建把把智能")
                //条款地址
 .termsOfServiceUrl("http://despairyoke.github.io/")
                .contact("zwd")
                .version("1.0")
                //描述
 .description("API 描述")
                .build();
 }
}

注意:swagger的扫包路径,才能正确访问swagger
目录结构如下:
目录结构

IUserService中添加两个接口

void recordJob(User job);
@PostConstruct
void init();

实现类 UserServiceImpl中的代码如下:

package com.mybatis.plus.service.impl;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.mybatis.plus.entity.User;
import com.mybatis.plus.mapper.UserMapper;
import com.mybatis.plus.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
 * 

* 服务实现类 *

* * @author wulongbo * @since 2020-11-09 */ @Service("iUserService") public class UserServiceImpl extends ServiceImpl implements IUserService { private static Logger logger = LoggerFactory.getLogger(UserServiceImpl.class); //定义一个容量为10000的阻塞队列,BlockingQueue线程安全可以多个生产者同时put private BlockingQueue dataQueue = new LinkedBlockingQueue(10000); private List list = new ArrayList(); @Override public void recordJob(User job) { try { //put任务的方法,供生产者调用 dataQueue.put(job); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } @PostConstruct @Override public void init() { Thread thread = new Thread(() -> { logger.info("启动批量守护线程,启动时间{}", new Date(System.currentTimeMillis())); while (Boolean.TRUE) { User poll = null; boolean pollTimeOut = false; long startTime; long endTime; try { // poll时设置超时时间为2秒 poll = dataQueue.poll(2, TimeUnit.SECONDS); } catch (InterruptedException e) { logger.info("批量更新Job异常"); Thread.currentThread().interrupt(); } if (null != poll) { // poll到任务添加到List中 list.add(poll); } else { // poll超时,设置超时标志位 pollTimeOut = true; } // 如果任务List等于300或poll超时且List中还有任务就批量更新 if (list.size() == 300|| (pollTimeOut && !CollectionUtils.isEmpty(list))){ startTime = System.currentTimeMillis(); saveOrUpdateBatch(list); logger.info("Job任务批量更新{}条任务,耗时{}毫秒", list.size(), System.currentTimeMillis()-startTime); list.clear(); } } }); thread.setName("job-batchUpdate-deamon"); // 设置启动的线程为守护线程 直到jvm停了才停止 thread.setDaemon(true); thread.start(); } }

控制页面模拟一下插入操作 UserController代码如下:

package com.mybatis.plus.controller;
import com.mybatis.plus.entity.User;
import com.mybatis.plus.service.IUserService;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * 

* 前端控制器 *

* * @author wulongbo * @since 2020-11-09 */ @RestController @RequestMapping("/user") @CrossOrigin public class UserController { @Autowired private IUserService iUserService; @ApiOperation(value="添加用户信息", notes="添加用户信息") @ApiImplicitParams({ @ApiImplicitParam(paramType = "query", name = "name", dataType = "String", required = true, value = "姓名"), @ApiImplicitParam(paramType = "query", name = "age", dataType = "int", required = true, value = "年龄"), @ApiImplicitParam(paramType = "query", name = "email", dataType = "String", required = true, value = "邮箱") }) @PostMapping("/addUser") public boolean addUser(String name,Integer age,String email) { try { User user=new User(); user.setName(name); user.setAge(age); user.setEmail(email); iUserService.recordJob(user); }catch (Exception e){ return false; } return true; } }

启动项目并访问http://localhost:8080/swagger-ui.html#/
访问swagger

注意检查一下:

  1. User实体类中的主键id是否设置成自动递增
/**
 * 主键ID
 */@TableId(value = "id", type = IdType.AUTO)
private Long id;

2.数据库User表主键是否设置主键自动递增

填写用户信息,并调用添加用户信息接口
image.png
OK,证明我们的队列生效!这里便演示了使用 BlockingQueue 在并发情况下对mysql做的批量操作。

案例三:mysql之批量删除

这里读者已经可以体会 BlockingQueue 给我们带来的便利了,批量删除差异点就是在 对阻塞队列的定义上面 我们使用

//定义一个容量为10000的阻塞队列,BlockingQueue线程安全可以多个生产者同时put
private BlockingQueue delDataQueue = new LinkedBlockingQueue(10000);

来给生产者调用,【BlockingQueue,或者BlockingQueue依据实际的主键类型来创建就好】。

同理我们在 IUserService 中添加删除的接口

void delJob(User job);
@PostConstruct
void delInit();

再其实现类 UserServiceImpl 中添加实现方法

//定义一个容量为10000的阻塞队列,BlockingQueue线程安全可以多个生产者同时put
private BlockingQueue delDataQueue = new LinkedBlockingQueue(10000);
private List delList = new ArrayList();

@Override
public void delJob(User job) {
    try {
        //put任务的方法,供生产者调用
 delDataQueue.put(job.getId());
 } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
 }
}
@PostConstruct
@Override
public void delInit() {
    Thread thread = new Thread(() -> {
        logger.info("启动批量删除守护线程,启动时间{}", new Date(System.currentTimeMillis()));
 while (Boolean.TRUE) {
            Long poll = null;
 boolean pollTimeOut = false;
 long startTime;
 long endTime;
 try {
                // poll时设置超时时间为2秒
 poll = delDataQueue.poll(2, TimeUnit.SECONDS);
 } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
 }
            if (null != poll) {
                // poll到任务添加到List中
 delList.add(poll);
 } else {
                // poll超时,设置超时标志位
 pollTimeOut = true;
 }
            // 如果任务List等于5000或poll超时且List中还有任务就批量更新
 if (delList.size() == 300||
                    (pollTimeOut && !CollectionUtils.isEmpty(delList))){
                startTime = System.currentTimeMillis();
 removeByIds(delList);
 logger.info("Job任务批量删除{}条任务,耗时{}毫秒", delList.size(),
 System.currentTimeMillis()-startTime);
 delList.clear();
 }
        }
    });
 thread.setName("job-batchUpdate-deamon");
 // 设置启动的线程为守护线程 直到jvm停了才停止
 thread.setDaemon(true);
 thread.start();
}

控制页面模拟删除操作 UserController代码如下:

@ApiOperation(value="删除用户信息", notes="删除用户信息")
@ApiImplicitParams({
        @ApiImplicitParam(paramType = "query", name = "id", dataType = "long", required = true, value = "用户id")})
@PostMapping("/delUser")
public boolean delUser(Long id) {
    try {
        User user=new User();
 user.setId(id);
 iUserService.delJob(user);
 }catch (Exception e){
        return false;
 }
    return true;
}

我们在swagger参数中连续输入6,7,8并快速执行删除接口
image.png

可以看到控制台输出结果
image.png
至此便用 BlockingQueue 模拟了在并发情况下对数据库的批量删除操作。

你可能感兴趣的:(java)