Tomcat作为一个Java中流行高并发Web容器,使用了非常多的异步处理措施,其中就包括消费者-生产者模型
在默认的nio协议中,使用Accept线程接受用户请求,并将请求封装成Event提交到队列中,Poller线程从队列中消费数据,处理用户的请求
本次我们就来学习一下这种解耦神器-队列, Tomcat没有使用Java juc包提供的队列,而是自己写了一个:SynchronizedQueue,至于原因,官方的描述是这样的:
这主要是作为java.util.concurrent的无GC替代方案。ConcurrentLinkedQueue,当需要创建一个无限制的队列而不需要收缩队列时。其目的是以最少的垃圾尽快提供所需功能的最低限度。
核心属性
//用数组来存储元素
private Object[] queue;
//队列大小
private int size;
//插入索引(可以理解为队尾)
private int insert = 0;
//移除索引(可以理解为队头)
private int remove = 0;
核心方法
初始化
public SynchronizedQueue(int initialSize) {
//初始化数组大小
queue = new Object[initialSize];
size = initialSize;
}
入队:offer
public synchronized boolean offer(T t) {
queue[insert++] = t;
// 当数组写满当时候,写指针指向数组第一个元素以达到循环利用的目的
// tip:这里不用担心,下次写的时候会覆盖以前的元素。因为队列遵循先进先出的原则,
// 如果队列有移除过一个元素的话,0号元素已经出队列了,覆盖也就没什么影响。
// 如果没有移除过元素的话remove也是=0的,所以就会执行下面的扩容操作
if (insert == size) {
insert = 0;
}
//写指针和移除指针重合,表示已经没有位置写了,就该扩容
if (insert == remove) {
expand();
}
return true;
}
扩容:expand
private void expand() {
int newSize = size * 2;
//建立一个新数组:大小是原来的两倍
Object[] newQueue = new Object[newSize];
//这里采用了一个native的方法来实现数据拷贝,别问为什么,问就是因为高效
//此处拷贝的流程是这样的,假设有这样一个长度为8的数组:
// 索引: 0 1 2 3 4 5 6 7
// 元素: J A V A W U D I
//此时的insert=1,remove=1,队头元素是A,队尾元素是J
//拷贝到新数组应该是这样的
// 索引: 0 1 2 3 4 5 6 7 8 9 10 11 12 14 15 16
// 元素: A V A W U D I J
// 因为要保持先进先出的原则,所以拷贝的时候先拷贝:A V A W U D I
System.arraycopy(queue, insert, newQueue, 0, size - insert);
//再拷贝:J,因为J是最后进队列的,是队尾
System.arraycopy(queue, 0, newQueue, size - insert, insert);
//不理解的同学:请注意扩容时机,数组是完全满了之后再扩容的,结合这个再去看看上面的逻辑
insert = size;
remove = 0;
queue = newQueue;
size = newSize;
}
出队:poll
public synchronized T poll() {
//写指针和移除指针相等,表示队列还是空的。
// 因为元素插入之后,在offer里面进行判断处理:insert==remove就会扩容,
//扩容之后insert肯定不会等于remove
// 当所有元素的都移除之后,insert就会重新等于remove
if (insert == remove) {
// empty
return null;
}
@SuppressWarnings("unchecked")
T result = (T) queue[remove];
queue[remove] = null;
remove++;
//数组最后一个元素移除完了,下次该移除数组第一个元素了
if (remove == size) {
remove = 0;
}
return result;
}
总结
SynchronizedQueue作为一个环形队列,除了在空间利用率上很高效,同时还是线程安全,在核心方法offer、poll、size、clear都使用了synchronized关键字来保证在多线程环境下的安全。
学到了吧,赶紧用起来,下回项目中要用什么队列的时候,就把这复制过去,记得改成自己的专属名字 ~~~~~~~hhh学到就装到