一:前言

  写这个程序主要是用来理解生产者消费者模型,以及通过这个Demo来理解Redis的单线程取原子任务是怎么实现的和巩固一下并发相关的知识;这个虽然是个Demo,但是只要稍加改下Appender部分也是可以用于项目中的,假设项目里确实不需要log4j/logback之类的日志组件的时候;

二:实现方式

1.利用LinkedList作为MQ(还可以用jdk自带的LinkedBlockingQueue,不过这个Demo主要是为了更好的理解原理因此写的比较底层);

2.利用一个Daemon线程作为消费者从MQ里实时获取日志对象/日志记录,并将它提交给线程池,由线程池再遍历所有的appender并调用它们的通知方法,这个地方还可以根据场景进行效率优化,如将循环遍历appender改为将每个appender都再此提交到线程池实现异步通知观察者;

3.为生产者提供log方法作为生产日志记录的接口,无论是生产日志对象还是消费日志对象在操作队列时都需要对队列加锁,因为个人用的是非并发包里的;

4.消费者在获取之前会先判断MQ里是否有数据,有则获取并提交给线程池处理,否则wait;

5.生产者生产了日志对象后通过notify通知消费者去取,因为只有一个消费者,而生产者是不会wait的因此只需要notify而不用notifyAll

6.。。剩下的就看代码来说明吧;

三:代码

1.MyLogger类的实现

package me.silentdoer.mqlogger.log;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import static me.silentdoer.mqlogger.log.MyLogger.LogLevel.DEBUG;
import static me.silentdoer.mqlogger.log.MyLogger.LogLevel.ERROR; /**
* @author silentdoer
* @version 1.0
* @description 这里只是做一个简单的logger实现,不提供Appender之类的功能,主要是用来学习生产者和消费者及MQ的实现原理
* @date 4/26/18 6:07 PM
*/
public class MyLogger{
private LogLevel loggerLevel = DEBUG;
private String charset = "UTF-8"; // 暂且没用,但是当需要序列化时是可能用到的;
// TODO 也可以直接用LinkedQueue,然后手动通过ReentrantLock来实现并发时的数据安全(synchronized也可)
//private BlockingQueue<LogRecord> queue = new LinkedBlockingQueue<LogRecord>(); // 可以理解为支持并发的LinkedList
// TODO 想了一下既然是要学习原理干脆就实现的更底层一点
private final Queue<LogRecord> records = new LinkedList<LogRecord>();
// TODO 用于记录生产了多少条日志,可供外部获取
private AtomicLong produceCount = new AtomicLong(0);
// TODO 用于记录消费了多少条日志
private AtomicLong consumeCount = new AtomicLong(0);
// TODO 日志记录的Consumer
private Thread consumer = new LogDaemon(); public MyLogger(){
consumer.setDaemon(true);
consumer.start();
} /**
* 对外提供的接口,即log方法就是生产者用于生产日志数据的接口
* @param msg
* @param level
*/
public void log(String msg, LogLevel level){
Date curr = generateCurrDate();
log(new LogRecord(level, msg, curr));
} /**
* 对外提供的接口,即log方法就是生产者用于生产日志数据的接口
* @param msg
*/
public void log(String msg){
Date curr = generateCurrDate();
log(new LogRecord(this.loggerLevel, msg, curr));
} /**
* 给生产者(即调用log的方法都可以理解为生产者在生产日志对象)提供用于生产日志记录的接口
* @param record
*/
public void log(LogRecord record){
// ReentrantLock可以替代synchronized,不过当前场景下synchronized已经足够
synchronized (this.records){ // TODO 如果用的是LinkedBlockingQueue是不需要这个的
this.records.offer(record);
this.produceCount.incrementAndGet();
this.records.notify(); // TODO 只有一个线程会records.wait(),因此notify()足够
}
} // TODO 类似Redis的那个单线程,用于读取命令对象,而这里则是用于读取LogRecord并通过appender将数据写到相应位置
private class LogDaemon extends Thread{
private volatile boolean valid = true;
// 充当appenders的角色
private List<Writer> appenders = null;
private ExecutorService threadPool = new ThreadPoolExecutor(1, 3
, 180000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024)); @Override
public void run() {
while(this.valid){
// TODO 根据最少知道原则,在这里不要去想整体里是否存在打断此线程的地方,你就认为此线程是可能被外界打断的即可,因此需要做一定处理
try {
synchronized (MyLogger.this.records) {
if (MyLogger.this.records.size() <= 0) {
MyLogger.this.records.wait();
}
final LogRecord firstRecord = MyLogger.this.records.poll();
MyLogger.this.consumeCount.incrementAndGet();
//threadPool.submit()
threadPool.execute(() -> MyLogger.this.notifyAppender(this.appenders, firstRecord));
}
}catch (InterruptedException ex){
this.valid = false;
ex.printStackTrace();
}catch (Throwable t){
t.printStackTrace();
}
}
}
} private void notifyAppender(final List<Writer> appenders, final LogRecord record) {
if(appenders == null){
PrintWriter writer = new PrintWriter(record.level == ERROR ? System.err : System.out);
writer.append(record.toString());
writer.flush();
}else{
// TODO 这种是同步的方式,如果是异步的方式可以将每个appender的执行都由一个Runnable对象包装,然后submit给线程池(或者中间加个中间件)
for(Writer writer : appenders){
try {
writer.append(record.toString());
}catch (IOException ex){
ex.printStackTrace();
}
}
}
} /**
* 用于产生当前时间的模块,防止因为并发而导致LogRecord的timestamp根实际情况不符
*/
private Lock currDateLock = new ReentrantLock(); // 直接用synchronized亦可
private Date generateCurrDate(){
currDateLock.lock();
Date result = new Date();
currDateLock.unlock();
return result;
} // 生产者生产的数据对象
public static class LogRecord{
private LogLevel level;
private String msg;
private Date timestamp;
private static final SimpleDateFormat DEFAULT_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
private SimpleDateFormat dateFormat = DEFAULT_DATE_FORMAT; /*public LogRecord(){
this(INFO, "");
}*/ public LogRecord(LogLevel level, String msg){
this(level, msg, new Date()); // 还是最好由外界设置timestamp,否则高并发下会比较不准
} // TODO 最好用这个,不然高并发下timestamp容易出现顺序不准确的情况。
public LogRecord(LogLevel level, String msg, Date timestamp){
this.level = level;
this.msg = msg;
this.timestamp = timestamp;
} @Override
public String toString(){
return String.format("[Level:%s, Datetime:%s] : %s\n", level, dateFormat.format(timestamp), msg);
} public LogLevel getLevel() {
return level;
} public String getMsg() {
return msg;
} public void setDateFormat(SimpleDateFormat dateFormat) {
this.dateFormat = dateFormat;
} public void setTimestamp(Date timestamp) {
this.timestamp = timestamp;
}
} public enum LogLevel{ // TODO 内部enum默认就是static
INFO,
DEBUG,
ERROR
} public LogLevel getLoggerLevel() {
return loggerLevel;
} public void setLoggerLevel(LogLevel loggerLevel) {
this.loggerLevel = loggerLevel;
} public String getCharset() {
return charset;
} public void setCharset(String charset) {
this.charset = charset;
} public AtomicLong getProduceCount() {
return produceCount;
} public AtomicLong getConsumeCount() {
return consumeCount;
}
}

2.测试用例1

package me.silentdoer.mqlogger;

import me.silentdoer.mqlogger.log.MyLogger;

import java.util.Scanner;

/**
* @author silentdoer
* @version 1.0
* @description the description
* @date 4/26/18 10:13 PM
*/
public class Entrance {
private static MyLogger logger = new MyLogger(); public static void main(String[] args){
//logger.setLoggerLevel(MyLogger.LogLevel.ERROR);
Scanner scanner = new Scanner(System.in);
String line;
while(!(line = scanner.nextLine()).equals("exit")){
if(line.equals(""))
continue;
logger.log(line);
System.out.println(String.format("共生产了%s条日志。", logger.getConsumeCount()));
try {
Thread.sleep(500);
}catch (InterruptedException ex){ }
System.out.println(String.format("共消费了%s条日志。", logger.getProduceCount()));
}
}
}

3.测试用例2

package me.silentdoer.mqlogger;

import me.silentdoer.mqlogger.log.MyLogger;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; /**
* @author silentdoer
* @version 1.0
* @description the description
* @date 4/26/18 10:32 PM
*/
public class Entrance2 {
private static MyLogger logger = new MyLogger(); public static void main(String[] args){
logger.setLoggerLevel(MyLogger.LogLevel.ERROR);
ExecutorService threadPool = Executors.newCachedThreadPool();
for(int i=0;i<10;i++){
final int index = i + 1;
threadPool.execute(() -> {
logger.log(String.format("生产的第%s条记录。", index));
System.out.println(String.format("共生产了%s条记录。", index));
});
try {
Thread.sleep(100);
}catch (InterruptedException ex){ }
}
try {
Thread.sleep(3000);
System.out.println(String.format("共%s条记录被消费。", logger.getConsumeCount()));
}catch (InterruptedException ex){ }
//threadPool.shutdown();
//threadPool.shutdownNow();
}
}

四:补充

  如果想实现像BlockingQueue一样能够控制MQ的元素个数范围,则可以通过ReentrantLock的Confition来实现,即通过lock创建两个Condition对象,一个用来描述是否MQ中元素达到上限的情况,一个用于描述MQ中元素降到下限的情况;

无论是达到上限或降到下限都会通过相应的condition对象来阻塞对应的生产者或消费者的生产/消费过程从而实现MQ元素个数的可控性;

利用生产者消费者模型和MQ模型写一个自己的日志系统-并发设计里一定会用到的手段的更多相关文章

  1. 复杂领域的Cynefin模型和Stacey模型

    最近好奇“复杂系统”,收集了点资料,本文关于Cynefin模型和Stacey模型.图文转自互联网后稍做修改. Cynefin模型提供一个从因果关系复杂情度来分析当前情况而作决定的框架,提出有五个领域: ...

  2. 文本信息检索——布尔模型和TF-IDF模型

    文本信息检索--布尔模型和TF-IDF模型 1. 布尔模型 ​ 如要检索"布尔检索"或"概率检索"但不包括"向量检索"方面的文档,其相应的查 ...

  3. 贫血模型和DDD模型

    贫血模型和DDD模型 1.贫血模型 1.1 概念 常见的mvc三层架构 简单.没有行为 2.领域驱动设计 2.1 概念(2004年提出的) Domain Driven Design 简称 DDD DD ...

  4. 并发编程:Actors 模型和 CSP 模型

    https://mp.weixin.qq.com/s/emB99CtEVXS4p6tRjJ2xww 并发编程:Actors 模型和 CSP 模型 ImportNew 2017-04-27    

  5. 三分钟掌控Actor模型和CSP模型

    回顾一下前文<三分钟掌握共享内存模型和 Actor模型> Actor vs CSP模型 传统多线程的的共享内存(ShareMemory)模型使用lock,condition等同步原语来强行 ...

  6. Python之小测试:用正则表达式写一个小爬虫用于保存贴吧里的所有图片

    很简单的两步: 1.获取网页源代码 2.利用正则表达式提取出图片地址 3.下载 #!/usr/bin/python #coding=utf8 import re # 正则表达式 import urll ...

  7. 使用PHP文件锁写一个多个请求同时并发写入一个文件,要求不脏读、数据不丢失

    使用PHP文件锁写一个多个请求同时并发写入一个文件,要求不脏读.数据不丢失. //并发文件操作 function filehandle($filename,$data){ $start = 0; $e ...

  8. java——利用生产者消费者模式思想实现简易版handler机制

    参考教程:http://www.sohu.com/a/237792762_659256 首先说一下这里面涉及到的线程: 1.mainLooper: 这个线程可以理解为消费者线程,里面运行了一个死循环, ...

  9. Inception模型和Residual模型卷积操作的keras实现

    Inception模型和Residual残差模型是卷积神经网络中对卷积升级的两个操作. 一.  Inception模型(by google) 这个模型的trick是将大卷积核变成小卷积核,将多个卷积核 ...

随机推荐

  1. python xml.dom模块解析xml

    1. 什么是xml?有何特征? xml即可扩展标记语言,它可以用来标记数据.定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言. 例子:del.xml <?xml version=&q ...

  2. 上传视频使用ffmpeg自动截取缩略图

    上传视频之后,有的需要显示缩略图,而不是仅仅显示视频名称的列表,这时候就需要对上传的视频截取缩略图. 简单粗暴点,将以下代码作为工具类复制粘贴即可: package com.util; import ...

  3. python基础——列表推导式

    python基础--列表推导式 1 列表推导式定义 列表推导式能非常简洁的构造一个新列表:只用一条简洁的表达式即可对得到的元素进行转换变形 2 列表推导式语法 基本格式如下: [expr for va ...

  4. SpringMVC(七):@RequestMapping下使用POJO对象绑定请求参数值

    Spring MVC会按照请求参数名和POJO属性名进行自动匹配,自动为该对象填充属性值,支持级联属性. 如:address.city.dept.address.province等. 步骤一:定义Ac ...

  5. Struts(十):OGNL表达式(一)

    Struts2 用s:porperty标签和OGNL表达式来读取值栈中的属性值: I.值栈中的属性值: 1.对象栈:读取对象栈中的某一个对象的属性值: 2.Map栈 :request,session, ...

  6. Canvas-自由绘制

    #自由绘制 from tkinter import * master=Tk() c=Canvas(master,width=400,height=200) c.pack() def paint(eve ...

  7. 1102mysql关于SOCK文件的认识

    自己原创,只为记录. 当本地登录MySQL的时候提示"Can't connect to local MySQL server through socket"的问题,其实代码是MyS ...

  8. Python中的上下文管理器和with语句

    Python2.5之后引入了上下文管理器(context manager),算是Python的黑魔法之一,它用于规定某个对象的使用范围.本文是针对于该功能的思考总结. 为什么需要上下文管理器? 首先, ...

  9. CSS 权威指南 CSS实战手册 第四版(阅读笔记)

    前言: 对于程序员,学习是无止境的,知识淘换非常快,能够快速稳固掌握一门新技术,是一个程序员应该具备的素质.这里将分析本人一点点不成熟的心得. 了解一门语言,了解它的概念非常重要,但是一些优秀的设计思 ...

  10. selenium chrome浏览器与chrome.driver的对应关系

    看到网上基本没有最新的chromedriver与chrome的对应关系表,便兴起整理了一份如下,希望对大家有用: chromedriver版本 支持的Chrome版本 v2.34 v61-63 v2. ...