引言
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#/
注意检查一下:
User
实体类中的主键id是否设置成自动递增
/**
* 主键ID
*/@TableId(value = "id", type = IdType.AUTO)
private Long id;
2.数据库User表主键是否设置主键自动递增
填写用户信息,并调用添加用户信息接口
连按多下(作者按了3次),测试 BlockingQueue
是否把User对象都缓存到了阻塞队列中,再一次性消费掉。
可以看到控制台打印日志:
核对数据库表,查看是否成功插入数据
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并快速执行删除接口
可以看到控制台输出结果
核对数据库表中记录是否已经删除,发现确实已经删除
至此便用 BlockingQueue
模拟了在并发情况下对数据库的批量删除操作。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。