引言

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<String> queue = new LinkedBlockingQueue<String>(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<String> queue;
 private static final int DEFAULT_RANGE_FOR_SLEEP = 1000;
 //构造函数
 public Consumer(BlockingQueue<String> 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依赖:

<!-- 引入swagger2依赖-->
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
 <groupId>io.springfox</groupId>
 <artifactId>springfox-swagger2</artifactId>
 <version>2.9.2</version>
</dependency>
<dependency>
 <groupId>io.springfox</groupId>
 <artifactId>springfox-swagger-ui</artifactId>
 <version>2.9.2</version>
</dependency>

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;
/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author wulongbo
 * @since 2020-11-09
 */
@Service("iUserService")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
    private static Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
 //定义一个容量为10000的阻塞队列,BlockingQueue线程安全可以多个生产者同时put
 private BlockingQueue<User> dataQueue = new LinkedBlockingQueue<User>(10000);
 private List<User> list = new ArrayList<User>();
 @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;
/**
 * <p>
 * 前端控制器
 * </p>
 *
 * @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表主键是否设置主键自动递增

填写用户信息,并调用添加用户信息接口
调用swagger
连按多下(作者按了3次),测试 BlockingQueue是否把User对象都缓存到了阻塞队列中,再一次性消费掉。
可以看到控制台打印日志:
image.png
核对数据库表,查看是否成功插入数据
image.png
OK,证明我们的队列生效!这里便演示了使用 BlockingQueue 在并发情况下对mysql做的批量操作。

案例三:mysql之批量删除

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

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

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

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

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

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

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

@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
核对数据库表中记录是否已经删除,发现确实已经删除
image.png
至此便用 BlockingQueue 模拟了在并发情况下对数据库的批量删除操作。


isWulongbo
228 声望26 粉丝

在人生的头三十年,你培养习惯,后三十年,习惯铸就你