java并发的发布和订阅测试
现在编码的时候,为了处理消息,大家动不动就上个重器,例如MQ之类的。但很多时候,并不是那么有必要,因为数据量和并发其实远远不够。
可以替代的方案非常多,其中一个是java.util.concurrent。
在jdk9及其以上,java.util.Observable已经被标注为过时,官方推荐使用java.beans或者是java.util.concurrent。
在发布订阅者模式中,有四个对象是需要关注的:
- 发布者
- 订阅者(消费者)
- 消息
- 并发
本文主要代码参考 https://www.cnblogs.com/zhangmingda/p/14715139.html。
不过为了更加友好一些,对原有的代码做了适量的调整。
注:以下代码运行于windows11+JDK17。
消息对象
package study.base.designPattern.observer.latest.flow; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class Message { private Integer qty; private Integer maxQty; private String content; private Map<String, Boolean> readStatus; public Map<String, Boolean> getReadStatus() { return readStatus; } public String getContent() { return content; } public boolean isFinished() { return qty.intValue() == maxQty.intValue(); } public Message(String content, List<String> readerList) { this.content = content; this.maxQty = readerList.size(); this.qty = 0; this.readStatus = new ConcurrentHashMap<>(); for (String item : readerList) { readStatus.put(item, false); } } public void read(String readerName) { if (qty < maxQty) { if (readStatus.containsKey(readerName)) { Boolean isRead = readStatus.get(readerName); if (!isRead) { synchronized (readStatus) { readStatus.put(readerName, true); qty++; System.out.println("---|"+readerName + "正在进行" + this.content + "...." + qty); if (qty==maxQty) { System.out.println("\n---|所有人已经完成【"+this.content+"】\n"); } } } } } } }
消费者对象
package study.base.designPattern.observer.latest.flow; import java.util.concurrent.Flow.Subscriber; import java.util.concurrent.Flow.Subscription; public class Student implements Subscriber<Message> { private Subscription subscription; private String name; public Student(String name) { this.name = name; } @Override public void onSubscribe(Subscription subscription) { System.out.println(name + "开始订阅[" + subscription.toString() + "]"); subscription.request(1); this.subscription = subscription; } @Override public void onError(Throwable e) { System.out.println(e.getMessage()); } @Override public void onComplete() { System.out.println(name + "关闭了订阅"); Teacher.getLock().lock(); Teacher.getCondition().signalAll(); Teacher.getLock().unlock(); } @Override public void onNext(Message message) { message.read(this.name); subscription.request(1); /** * 模拟延时--郊游的时候,时间长一些 */ try { if (message.getContent().equals("郊游")) { Thread.sleep(1300); } else { Thread.sleep(200); } } catch (InterruptedException e) { e.printStackTrace(); } } }
发布者和主程序
package study.base.designPattern.observer.latest.flow; import java.util.Arrays; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.SubmissionPublisher; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.serializer.SerializerFeature; /** * https://www.cnblogs.com/zhangmingda/p/14715139.html 此类技术是观察者模式的一个并发实现 * * @author lzfto */ public class Teacher { private static Lock lock = new ReentrantLock(true); private static Condition condition = lock.newCondition(); private volatile static CopyOnWriteArrayList<Message> subjectList = new CopyOnWriteArrayList<Message>(); public static Lock getLock() { return lock; } public static Condition getCondition() { return condition; } public static void main(String[] args) throws InterruptedException { /** * 定义一个发布者,需要设定要发送消息的泛型数据类型 */ SubmissionPublisher<Message> teacher = new SubmissionPublisher<>(); /** * 定义一个订阅者 */ List<String> studentList = Arrays.asList("mzd", "***", "luzhfiei", "海瑞"); for (String readerName : studentList) { Student student = new Student(readerName); teacher.subscribe(student); } /** * 测试发布消息 */ subjectList.add(new Message("朗读", studentList)); subjectList.add(new Message("劳动", studentList)); subjectList.add(new Message("郊游", studentList)); subjectList.add(new Message("射箭", studentList)); subjectList.forEach(item -> { System.out.println("同学们!开始【" + item.getContent() + "】"); teacher.submit(item); }); // 向订阅者发布数据,需要保持主线程存活,否则当前线程执行结束,发布者和订阅者都被销毁了。 /** * 关闭消息发布 */ teacher.close(); // 关闭后,如果当前线程未退出,待订阅者所有消息都处理完毕才会运行订阅者的onComplete方法 lock.lock(); condition.await(); lock.unlock(); System.out.println(JSONArray.toJSONString(subjectList, SerializerFeature.PrettyFormat)); ; } }
测试后的输出(一种情况):
mzd开始订阅[java.util.concurrent.SubmissionPublisher$BufferedSubscription@12749d9e] ***开始订阅[java.util.concurrent.SubmissionPublisher$BufferedSubscription@1df086d4] luzhfiei开始订阅[java.util.concurrent.SubmissionPublisher$BufferedSubscription@1e7629c2] 海瑞开始订阅[java.util.concurrent.SubmissionPublisher$BufferedSubscription@73e441ec] 同学们!开始【朗读】 同学们!开始【劳动】 同学们!开始【郊游】 同学们!开始【射箭】 ---|mzd正在进行朗读....1 ---|海瑞正在进行朗读....2 ---|luzhfiei正在进行朗读....3 ---|***正在进行朗读....4 ---|所有人已经完成【朗读】 ---|海瑞正在进行劳动....1 ---|luzhfiei正在进行劳动....2 ---|***正在进行劳动....3 ---|mzd正在进行劳动....4 ---|所有人已经完成【劳动】 ---|***正在进行郊游....1 ---|mzd正在进行郊游....2 ---|luzhfiei正在进行郊游....3 ---|海瑞正在进行郊游....4 ---|所有人已经完成【郊游】 ---|mzd正在进行射箭....1 ---|***正在进行射箭....2 ---|luzhfiei正在进行射箭....3 ---|海瑞正在进行射箭....4 ---|所有人已经完成【射箭】 luzhfiei关闭了订阅 ***关闭了订阅 mzd关闭了订阅 海瑞关闭了订阅
subjectList的结果:
[ { "content": "朗读", "finished": true, "readStatus": { "mzd": true, "海瑞": true, "***": true, "luzhfiei": true } }, { "content": "劳动", "finished": true, "readStatus": { "mzd": true, "海瑞": true, "***": true, "luzhfiei": true } }, { "content": "郊游", "finished": true, "readStatus": { "mzd": true, "海瑞": true, "***": true, "luzhfiei": true } }, { "content": "射箭", "finished": true, "readStatus": { "mzd": true, "海瑞": true, "***": true, "luzhfiei": true } } ]
这只是一个非常简单的例子,距离生产还有很远的一个距离。