BlockingQueue:是一个阻塞队列,队列空间满了就阻塞起来,不能再往里面放数据,等有空间了再放,比如初始化大小为1的队列,就是只能往里面put一个数据进去,当此时再往里面put时,该put线程就会被挂起,添加的元素会被放在notFull的队列中等待,当队列poll()获取并移除数据之后,就有空余了,put线程会被唤醒,然后从notFull中依次put进去。这个我们可以在抢购中去使用,比如当商品只剩1个了时,有两个用户同时抢购,然后同时进行持久层处理,那岂不是会两个人都抢到了?这时我们可以使用队列,将抢购用户放入队列中,我们从队列里依次取出用户进行处理。比如我们使用socket调用第三方接口,第三方提供三个socket监听端口,这时候高并发,如果我们不用队列,就会出现同时都是使用socket("9.234.20.93",8086),这时候就会造成数据乱了,也会出现异常。所以使用队列一个一个来。
@Async:异步调用注解。异步就是啊,我们在访问其他接口或是执行某个任务时比较缓慢,不能让程序一直在这个任务上耗时呀,所以就是发了调用或是执行指令,然后不需要等待它执行完,我们就先去执行其他任务,之后在后面某个地方再去获取值。比如有A、B、C三个任务,我执行A后,去执行B,可是B有点慢,那我就异步执行B,不需要等执行完毕,我直接去执行C了,然后等执行完C之后,我再去获取B的结果值。(开启一个线程去执行B,然后主线程去执行C了,执行完C之后,就获取子线程的结果值)。
Future:这个是用来判断线程是否完成、中断任务、获取线程执行结果。
CountDownLatch:算是一个计数器吧。await()使线程处于等待状态,直到countdown结束后,再停止所有线程的工作CountDownLatch latch = new CountDownLatch(1);创建大小为1的CountDownLatch,latch.countdown()
一般Future、CountDownLatch两个一起使用,使异步变成同步。举例子:
(一):比如有个A任务,异步调用,然后之后在某个地方获取它的结果值,这时候如果线程还没结束,任务还没结束,那么获取的返回值肯定是空的,所以我们得保证线程执行完成,能够获取到返回值,这时,我们在获取值方法那里调用countdownlatch的wait()方法,让线程等待着,直到countdown结束,什么情况下使countdown结束呢?一用future的isdone方法来判断线程是否结束,结束的话就countdown(),或是自己构造一个方法表示线程执行结束,然后就countdown()。直到countdown为0之后,就可以唤醒线程之后返回结果值了。
(2):比如有A、B、C三个功能,A是验证身份证,B是验证信用、C是验证手机号。这三个验证都成功才算验证通过,这三个都一个失败验证就算失败。然后这三个功能我们不可能一步一步来呀,可以用异步方式去验证,我们可以创建大小为3的线程池,同时去执行A、B、C三个功能,然后在Future里创建大小为3的CountDownLatch,当每个任务执行成功就countdown()一次,然后future返回成功结果,当有个任务执行失败就countdown()3次然后future返回失败结果。之后我们就从future里获取三个功能的执行结果了。然后这就是所谓的把异步变成同步了。
下面是实例:
(一):乘客刷银联卡进闸,这时候会进行预授权,所以就得调用第三方接口发预授权报文给银联,然后读取银联返回的预授权结果。如果预授权成功就允许进闸,如果是预授权失败是黑名单等等就不能进闸。这时候访问第三方接口,肯定是很耗时的,所以我们使用异步,直接发送报文之后就去执行其他操作,然后再在某时候读取预授权结果。所以就是读和写分开了。
步奏:
写操作,就是发送报文的操作
1、创建大小为1的阻塞队列BlockingQueue<byte[]> writeQueue = new ArrayBlockingQueue<>(1),然后将要发送的报文放入队列中。
2、创建socket = new Socket(host, port()),因为银联提供的访问接口不可能只有一个,所以要配置好host和port,然后用redis维护,正在使用的host和post当做key放入redis中有效时间是12毫秒,所以创建socket时判断该key是否存在,不存在才能用,存在的话就不能用,用下一个。
3、然后获取OutputStream输出流,OutputStream os =socket.getOutputStream();算是往socket的流管道里写入报文吧。
4、然后是发送心跳包,因为不知道银联那边的服务是否正常,如果不正常我们也发报文过去,肯定是没响应的呀。所以我们发报文之前就先发个心跳包去测测银联那边的服务是否正常的。心跳包就算是发个空的字节过去吧,然后那边有返回响应就代表服务正常了。
socketClient.writeQueue.put(new byte[2]); 往队列中放入长度为2的空字节 byte[] data = null; while ((data = writeQueue.peek()) == null) { //从队列中读取数据 } os.write(data); //往输出流中写入数据 os.flush(); writeQueue.poll(); //将队列中的数据移除 |
然后是发送报文。就是先把报文放入队列中,然后我们再从对列中取出报文就是写操作。
socketClient.writeQueue.put(data); //将数据放入队列。 byte[] data = null; |
读操作,就是读取结果的操作。
1、读操作这里用到异步,可以使用spring的@Async异步注解,在读的方法前面加上@Async("unionPayPool"),并在主类加上@EnableAsync,然后unionPayPool是一个线程池,单例的线程池。
@Configuration @Bean("unionPayPool") } |
2、创建输入流InputStream is =socket.getInputStream();算是从socket的流管道中读取数据吧。
3、读取数据,这时候要判断读取的数据是心跳包的响应还是结果的响应,如果是心跳包,就写回响应。
while ((readLength = is.read(resultLen)) != -1) { int len = Integer.parseInt(String.format("%02x%02x", resultLen[0], resultLen[1]), 16); // 如果是心跳包,先写回复 if (len == 0) { log.info("客户机与银联{}心跳交互", socket); writeQueue.put(new byte[2]); write(); continue; } |
如果不是心跳包,就会解析返回结果的啦,因为报文和结果一般都会加密,毕竟是关乎到钱的问题,安全性要保证,这里我就直接取结果了,不想解密啦。
result = new byte[len + 2]; System.arraycopy(resultLen, 0, result, 0, 2); int isLen = -1; int i = 2; while ((isLen = is.read(result, i, result.length - i)) != -1 && i < result.length) { i += isLen; } } |
4、然后就是把返回的结果放入map中。然后用future维护啦。
Map<String, MsgFuture> readMap = new ConcurrentHashMap<>(); String id =(result[2].getData()+result[4].getData()) .substring(2, 8); |
所以就是在future的setmsg方法那里进行countdown(),因为setmsg之后就表示预授权已经完成啦。
public void setMsg(byte[] msg) { this.msg = msg; latch.countDown(); } |
5、然后在某处获取预授权结果时,我们得保证预授权完成,并且有结果值,所以我们在future的getData那里使用CountDownLatch让线程进站等待,直到countdown()结束。上面有说setData那里进行了countdown()了,所以一进行了countdown(),就唤醒之前然后等待的线程啦。因为设置的CountDownLatch大小为1,所以countdown()一次就唤醒等待的线程了。
public class MsgFuture implements Future<byte[]> { public MsgFuture() { @Override @Override @Override @Override @Override //setsetMsg()之后就会countDown(),就算是执行了这个方法之后,之前等待的主线程就会 被唤醒了。 public void setMsg(byte[] msg) { } |