1 引入
比如A在干一件事情,走不开不能中断,此时快递员来送快递,你走不开,只能等做完这件事,再去拿快递,让快递员先放到快递柜。
再比如tomcat中,当Tomcat处理请求到限制,又来请求,处理不了,就会把请求放到一个队列中等空闲再去处理
要点:
- 有一个结果需要从一个线程传递到另一个线程,他们之间需要关联同一个GuardedObject
- 如果有结果不断的来,可以讲结果放到一个队列中
2 简单的实现
2.1 第一版
定义一个GuardedObject,核心方法就是两个,获取结果,提交结果,在获取结果的时候,如果没有获取到,那就等待;提交结果的时候,为了防止另一个线程已经进入了等待,需要唤醒那些正在等待的线程
package study.wyy.concurrency.guared.demo1;
public class GuardObject<T> {
private T result;
/*****
* 获取结果
* @return
*/
public T get(){
synchronized (this){
while (null == result){
// 没有结果时候,就等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return result;
}
}
/*****
* 提交结果
* @return
*/
public void submit(T result){
synchronized (this){
this.result = result;
// 唤醒等待结果的线程,来获取结果
this.notifyAll();
}
}
}
测试
package study.wyy.concurrency.guared.demo1;
import lombok.extern.slf4j.Slf4j;
import java.util.Random;
import java.util.concurrent.TimeUnit;
@Slf4j
public class Test {
public static void main(String[] args) {
GuardObject<String> guardObject = new GuardObject<>();
Random random = new Random();
// 一个线程在从db进行数据的导出,
// 另一个线程负责将导出的数据进行写入文件,
new Thread(()->{
System.out.println("写入文件前置处理");
// 模拟前置处理的耗时
sleepSecond(random.nextInt(5));
// 获取另一个线程从数据库中读取到的数据
String res = guardObject.get();
System.out.println("开始写入文件。。。");
// 模拟写入数据的耗时
sleepSecond(random.nextInt(5));
System.out.println("写入文件结束。。。文件内容: " + res);
},"t1").start();
new Thread(()->{
// 读取数据库数据
System.out.println("读取数据库数据开始");
// 模拟读取数据的耗时
sleepSecond(random.nextInt(5));
String res = "hello word";
System.out.println("读取数据库数据结束");
// 提交数据
guardObject.submit(res);
},"t2").start();
}
private static void sleepSecond(int i) {
try {
TimeUnit.SECONDS.sleep(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果
写入文件前置处理
读取数据库数据开始
读取数据库数据结束
开始写入文件。。。
写入文件结束。。。文件内容: +hello word
2.2 扩展:增加超时时间
比如在get等待获取另一个线程的结果的时候,不能一直等待,有一个超时时间。
public T get(){
synchronized (this){
while (null == result){
// 不能在这一直等,需要一个超时时间
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return result;
}
}
wait本身就有一个重载方法,支持超时时间的,但是不能这么简单的就把超时时间通过wait来简单的控制:
public T get(long seconds){
synchronized (this){
while (null == result){
// 没有结果时候,就等待
try {
this.wait(seconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return result;
}
}
测试:
package study.wyy.concurrency.guared.demo1;
import lombok.extern.slf4j.Slf4j;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@Slf4j
public class Test2 {
public static void main(String[] args) {
GuardObject<String> guardObject = new GuardObject<>();
new Thread(()->{
try {
log.info("开始获取结果");
String res = guardObject.get(3);
log.info("拿到结果: => " + res);
} catch (TimeoutException e) {
e.printStackTrace();
}
},"t1").start();
new Thread(()->{
log.info("开始准备结果");
sleepSecond(5);
guardObject.submit("哈哈哈哈哈");
},"t2").start();
}
private static void sleepSecond(int i) {
try {
TimeUnit.SECONDS.sleep(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出:
20:58:03.374 [t1] INFO study.wyy.concurrency.guared.demo1.Test2 - 开始获取结果
20:58:03.377 [t2] INFO study.wyy.concurrency.guared.demo1.Test2 - 开始准备结果
20:58:08.382 [t1] INFO study.wyy.concurrency.guared.demo1.Test2 - 拿到结果: => 哈哈哈哈哈
- 可以发现:
- 3s的时候开始获取结果
- 拿到结果的时候却是8s,已经超时了
- 分析原因:
- wait在等待3s后,写入文件的线程会继续get,发现还是null,又继续wait了,和之前的没有实质的区别
所以这里需要我们自己计算等待时间:
- wait在等待3s后,写入文件的线程会继续get,发现还是null,又继续wait了,和之前的没有实质的区别
public T get(long timeoutSeconds) throws TimeoutException {
synchronized (this) {
// 开始时间
long beginTime = System.currentTimeMillis();
// 经历的时间
long passedTime = 0;
while (null == result) {
// 我设定的单位是秒,乘以1000是为了单位转换
if (passedTime >= timeoutSeconds * 1000 ) {
// 经历的时间已经大于我们的超时时间,就跳出循环
throw new TimeoutException();
}
try {
// 没有结果时候,就等待
// 注意的是这里的wait时间,超时时间减去已经等待的时间,防止虚假唤醒
// 比如,超时时间是3s
// 经历了1s,还没超时,被别人唤醒,但是get结果还是null,
// 这个时候,我们只能只需等待2s(超时时间-经历的时间)
this.wait(timeoutSeconds * 1000-passedTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 计算经历的时间
passedTime = System.currentTimeMillis() - beginTime;
}
return result;
}
}
简化一下代码
public T get(long timeoutSeconds) throws TimeoutException {
synchronized (this) {
// 开始时间
long beginTime = System.currentTimeMillis();
// 经历的时间
long passedTime = 0;
long waitTime = timeoutSeconds * 1000 - passedTime;
while (null == result) {
// passedTime大等于timeoutSeconds就是超时
// 根据waitTime = timeoutSeconds * 1000 - passedTime,也就是说
// waitTime <=0 也就是超时了
if (waitTime <= 0) {
// 经历的时间已经大于我们的超时时间,就跳出循环
throw new TimeoutException();
}
try {
// 没有结果时候,就等待
// 这里等到
this.wait(waitTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 计算经历的时间
passedTime = System.currentTimeMillis() - beginTime;
}
return result;
}
}
测试:
package study.wyy.concurrency.guared.demo1;
import lombok.extern.slf4j.Slf4j;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@Slf4j
public class Test2 {
public static void main(String[] args) {
GuardObject<String> guardObject = new GuardObject<>();
new Thread(()->{
try {
log.info("开始获取结果");
String res = guardObject.get(3);
log.info("拿到结果: => " + res);
} catch (TimeoutException e) {
log.error("time out,",e);
}
},"t1").start();
new Thread(()->{
log.info("开始准备结果");
sleepSecond(5);
guardObject.submit("哈哈哈哈哈");
},"t2").start();
}
private static void sleepSecond(int i) {
try {
TimeUnit.SECONDS.sleep(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出:
21:22:33.522 [t1] INFO study.wyy.concurrency.guared.demo1.Test2 - 开始获取结果
21:22:33.522 [t2] INFO study.wyy.concurrency.guared.demo1.Test2 - 开始准备结果
21:22:36.538 [t1] ERROR study.wyy.concurrency.guared.demo1.Test2 - time out,
java.util.concurrent.TimeoutException: null
at study.wyy.concurrency.guared.demo1.GuardObject.get(GuardObject.java:43)
at study.wyy.concurrency.guared.demo1.Test2.lambda$main$0(Test2.java:23)
at java.lang.Thread.run(Thread.java:748)
2.3 扩展二:增加队列
如果产生了过多结果,但是没有足够的消费者,就会丢失数据,就可以增加一个缓冲队列。
package study.wyy.concurrency.guared.demo1;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.IntStream;
/**
* @author by wyaoyao
* @Description
* @Date 2021/1/20 8:22 下午
*/
@Slf4j
public class Test3 {
public static void main(String[] args) {
GuardObject<String> guardObject = new GuardObject<>();
Random random = new Random();
new Thread(()->{
while (true){
log.info("开始获取结果");
String res = guardObject.get();
sleepSecond(2);
log.info("拿到结果: => " + res);
}
},"t1").start();
// 10个线程来生产数据
IntStream.range(1,10).forEach(i->
new Thread(()->{
guardObject.submit("第"+ i + "个数据");
},Integer.toString(i)).start()
);
}
private static void sleepSecond(int i) {
try {
TimeUnit.SECONDS.sleep(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
21:23:07.775 [t1] INFO study.wyy.concurrency.guared.demo1.Test3 - 开始获取结果
21:23:09.785 [t1] INFO study.wyy.concurrency.guared.demo1.Test3 - 拿到结果: => 第1个数据
21:23:09.785 [t1] INFO study.wyy.concurrency.guared.demo1.Test3 - 开始获取结果
21:23:11.789 [t1] INFO study.wyy.concurrency.guared.demo1.Test3 - 拿到结果: => 第9个数据
21:23:11.789 [t1] INFO study.wyy.concurrency.guared.demo1.Test3 - 开始获取结果
21:23:13.794 [t1] INFO study.wyy.concurrency.guared.demo1.Test3 - 拿到结果: => 第9个数据
21:23:13.795 [t1] INFO study.wyy.concurrency.guared.demo1.Test3 - 开始获取结果
21:23:15.799 [t1] INFO study.wyy.concurrency.guared.demo1.Test3 - 拿到结果: => 第9个数据
21:23:15.799 [t1] INFO study.wyy.concurrency.guared.demo1.Test3 - 开始获取结果
显然丢失数据了。
- 解决:加一个缓冲队列,当队列数据满了不能放数据。
2 案例二
- tomcat中,当Tomcat处理请求到限制,又来请求,处理不了,就会把请求放到一个队列中等空闲再去处理
- 定义一个请求实体:
package study.wyy.concurrency.guared;
/**
* @author: wyaoyao
* @Date: 2020/9/10 9:47 下午
* @Description: 请求
*/
public class Request {
// 简单模拟请求参数中的属性
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public Request(String value) {
this.value = value;
}
}
- 定义队列
package study.wyy.concurrency.guared;
import java.util.LinkedList;
/**
* @author: wyaoyao
* @Date: 2020/9/10 9:48 下午
* @Description: 请求队列
*/
public class RequestQueue {
LinkedList<Request> queue = new LinkedList<>();
/**
* @author: wyaoyao
* @Date: 2020/9/10 9:41 下午
* @Description: 服务端端通过该方法向队列中get请求
*/
public Request getRequest(){
synchronized (queue){
while (queue.size()<=0){
// 请求队列中没有请求,那就等着
try {
queue.wait();
} catch (InterruptedException e) {
// 当被唤醒的时候,需要break出去,进行工作,比如客户端请求来临的时候
return null;
}
}
// 请求队列中有请求数据,那就返回出去
return queue.removeFirst();
}
}
/**
* @author: wyaoyao
* @Date: 2020/9/10 9:41 下午
* @Description: 客户端通过该方法向队列中提交请求
*
*/
public void putRequest(Request request){
synchronized (queue){
queue.addLast(request);
// 唤醒其他线程 起来工作,在get的时候,可能没有请求数据,给wait了,一旦放入请求数据,自然就要唤醒
queue.notifyAll();
}
}
}
- client
package study.wyy.concurrency.guared;
import java.util.Random;
/**
* @author: wyaoyao
* @Date: 2020/9/10 9:47 下午
* @Description: 模拟客户端
*/
public class ClientThread extends Thread{
private final RequestQueue queue;
private final Random random;
public ClientThread(RequestQueue queue) {
this.queue = queue;
this.random = new Random(System.currentTimeMillis());
}
@Override
public void run() {
for(int i =0;i<10;i++){
// 模拟put 请求 10次
Request login_request = new Request("login request");
System.out.println("client -> request " + login_request.getValue());
queue.putRequest(login_request);
try {
Thread.sleep(random.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- server
package study.wyy.concurrency.guared;
import java.util.Objects;
import java.util.Random;
public class ServerThread extends Thread{
private final RequestQueue queue;
private final Random random;
/****
* 标记:是否关掉
*/
private volatile Boolean closed = false;
public ServerThread(RequestQueue queue) {
this.queue = queue;
this.random = new Random(System.currentTimeMillis());
}
@Override
public void run() {
while (!closed){
Request request = queue.getRequest();
if(Objects.isNull(request)){
// 忽略掉这个为null,这里不能break,那就跳出循环,服务就停了
continue;
}
System.out.println("server -> request " + request.getValue());
try {
// 模拟处理请求的耗时
Thread.sleep(random.nextInt(1000));
} catch (InterruptedException e) {
//e.printStackTrace();
// 这里收到中断信号(调用了closed),就return,关闭服务
return;
}
}
}
/**
* @Description 关闭服务
* @Author wyaoyao
* @Date 2020/9/10 9:57 下午
* @Param
* @Return
* @Exception
*/
public void closed(){
closed = true;
// 这里要打断,因为在get请求的时候可能因为队列中没有请求而wait,是无法判断到这个标记的
this.interrupt();
System.out.println("server closed");
}
}
- 测试
package study.wyy.concurrency.guared;
public class Test {
public static void main(String[] args) throws InterruptedException {
final RequestQueue requestQueue = new RequestQueue();
ServerThread serverThread = new ServerThread(requestQueue);
serverThread.start();
new ClientThread(requestQueue).start();
Thread.sleep(10000);
serverThread.closed();
}
}
client -> request login request
server -> request login request
client -> request login request
server -> request login request
client -> request login request
server -> request login request
client -> request login request
server -> request login request
client -> request login request
server -> request login request
client -> request login request
server -> request login request
client -> request login request
client -> request login request
server -> request login request
server -> request login request
client -> request login request
server -> request login request
client -> request login request
server -> request login request
server closed