public class SynchronizedStack{ //内部维护一个对象数组,用数组实现栈的功能 private Object[] stack; //这个方法用来归还对象,用synchronized进行线程同步 public synchronized boolean push(T obj) { index++; if (index == size) { if (limit == -1 || size < limit) { expand();//对象不够用了,扩展对象数组 } else { index--; return false; } } stack[index] = obj; return true; } //这个方法用来获取对象 public synchronized T pop() { if (index == -1) { return null; } T result = (T) stack[index]; stack[index--] = null; return result; } //扩展对象数组长度,以2倍大小扩展 private void expand() { int newSize = size * 2; if (limit != -1 && newSize > limit) { newSize = limit; } //扩展策略是创建一个数组长度为原来两倍的新数组 Object[] newStack = new Object[newSize]; //将老数组对象引用复制到新数组 System.arraycopy(stack, 0, newStack, 0, size); //将stack指向新数组,老数组可以被GC掉了 stack = newStack; size = newSize; } }
这个代码逻辑比较清晰,主要是 SynchronizedStack 内部维护了一个对象数组,并且用数组来实现栈的接口:push 和 pop 方法,这两个方法分别用来归还对象和获取对象。你可能好奇为什么 Tomcat 使用一个看起来比较简单的 SynchronizedStack 来做对象容器,为什么不使用高级一点的并发容器比如 ConcurrentlinkedQueue 呢?
这是因为 SynchronizedStack 用数组而不是链表来维护对象,可以减少结点维护的内存开销,并且它本身只支持扩容不支持缩容,也就是说数组对象在使用过程中不会被重新赋值,也就不会被 GC。这样设计的目的是用最低的内存和 GC 的代价来实现无界容器,同时 Tomcat 的最大同时请求数是有限制的,因此不需要担心对象的数量会无限膨胀。



