一、前言
在前一篇文章中分析filterChain的線程安全問題時講到了真正需要考慮線程安全問題與複用的場景是Request與Servlet的實例,本文重點分析一下Servlet實例的線程安全問題與池化複用機制。
二、正文
1. 什麼情況下Servlet實例會發生線程安全問題?
直接給答案:
(1)多個線程嘗試同時修改(未做synchronized/lock/condition等同步處理)同一個實例的非靜態成員變量(非Atomic類型)時有可能發生線程安全問題;
(2)多個線程嘗試同時修改(未做synchronized/lock/condition等同步處理)同一個類的靜態成員變量(非Atomic類型)時有可能發生線程安全問題;
以下情況不會發生線程安全問題:
(1)多個線程,但是每個線程有一個實例;
(2)多個線程,同一實例,但不同時修改實例成員變量,只做成員變量無關的可重入方法調用,因爲方法調用時使用到的JVM內存區域(棧)是線程私有的;
(3)多個線程,同一實例,同時修改實例成員變量,但是使用了同步機制(synchronized/lock/condition);
2. StandardWrapper對Servlet實例線程安全問題的處理
先給出分析得到的結論,後續再講分析過程:
主要過程在StandardWrapper類的allocate方法中體現;
(1)對於實現了SingleThreadModel接口的Servlet,會採用池化複用機制
容器維護了一個instancePool的棧用來保存創建出的實例,分配實例時鎖定instancePool對象,輪詢已分配實例數量與現有實例數量的關係,若現有實例不足則創建,直至達最大實例數,達最大還不足則等待其他線程通過deallocate釋放實例(也需鎖定instancePool)重新進入棧中,當現有實例數超過已分配實例數時,則從棧中彈出一個實例用於分配;
(2)對於未實現SingleThreadModel接口的Servlet
採用雙檢鎖的機制創建出Servlet單例返回即可;
(3)需要注意的地方
創建第一個實例之前,是不知道Servlet是否實現了SingleThreadModel接口的,因此在第一個實例創建出來之後需要進一步判斷是否爲STM模式,根據情況確定是直接返回還是需要將其放入實例池中。
3. StandardWrapper源碼分析
還是首先給出類關係圖:
前面的文章中已經提到過LifecycleMBeanBase是與JMX相關的內容,所以暫時忽略,那麼需要重點分析的就是幾個接口、ContainerBase與StandardWrapper;
首先來看幾個接口裏面都規定了些什麼:
(1)Notification與NotificationEmitter:看到第一句話“Interface implemented by an MBean that emits Notifications.”就決定不再往下看了,是與JMX相關的東西,暫不考慮,畢竟分析還是得抓重點;
(2)ServletConfig:表徵servlet配置對象,用於container與servlet之間傳遞信息,定義了四個方法,分別爲getServletName、getServletContext、getInitParameter、getInitParameterNames;
(3)Container:表徵容器對象,容器是能夠執行請求得到響應的對象,可以選擇性地支持一條Pipeline(包含多個Valve(實際處理請求的實體)),支持一些組件(Loader、Logger、Manager、Realm、Resources),容器分爲四種級別,由高到低分別爲Engine、Host、Context、Wrapper,可以嵌套,可以有父容器和子容器集合,接口中規定了一些容器相關屬性,Pipeline,監聽器,上下文,集羣以及父子容器的獲取方法;
(4)Wrapper:層級最低的容器,不允許有子容器,一個Wrapper表示了Web應用中一個獨立的Servlet,實現了對SingleThreadModel(後簡稱STM)的處理,管理Servlet實例的生命週期,定義的最主要的方法是allocate與deallocate,實現Servlet實例的分配與回收。
其次再看ContainerBase類:
ContainerBase是對Container接口的基礎實現,提供一些容器具備的基礎功能,對子類提供抽象方法invoke,在其內部定義了一條Pipeline用於組織調用多個Valve的invoke方法,通過Listener的方式實現了一些容器事件的通知,實現了對父子容器與集羣以及Realm(用於驗證用戶角色的安全領域實體,主要有authenticate/hasRole等方法,是一個只讀facade,一般被綁定於Context或級別更高的容器)的處理,實現了對自身及子容器後臺處理方法的調用;
最後來看StandardWrapper類:
源碼總共1800多行,全部一股腦分析的話有點冗餘,這裏只挑其中最重要的成員變量與方法進行分析,採用直接在源碼中加註釋的方式:
(1)成員變量:
public class StandardWrapper extends ContainerBase
implements ServletConfig, Wrapper, NotificationEmitter {
//日誌打印工具對象
private static final Log log = LogFactory.getLog(StandardWrapper.class);
//Servlet處理的默認請求方法
protected static final String[] DEFAULT_SERVLET_METHODS = new String[] {
"GET", "HEAD", "POST" };
// ----------------------------------------------------------- Constructors
/**
* Create a new StandardWrapper component with the default basic Valve.
*/
public StandardWrapper() {
super();
//把Pipeline的最後一個標準容器閥給new出來
swValve=new StandardWrapperValve();
pipeline.setBasic(swValve);
//JMX通知相關組件
broadcaster = new NotificationBroadcasterSupport();
}
// ----------------------------------------------------- Instance Variables
/**
* The date and time at which this servlet will become available (in
* milliseconds since the epoch), or zero if the servlet is available.
* If this value equals Long.MAX_VALUE, the unavailability of this
* servlet is considered permanent.
*/
//在什麼時間這個Servlet會變得可用,Long.MAX_VALUE表示永久不可用
protected long available = 0L;
/**
* The broadcaster that sends j2ee notifications.
*/
protected final NotificationBroadcasterSupport broadcaster;
/**
* The count of allocations that are currently active (even if they
* are for the same instance, as will be true on a non-STM servlet).
*/
//當前活躍的Servlet對象分配數量,使用原子量來保證線程安全
protected final AtomicInteger countAllocated = new AtomicInteger(0);
/**
* The facade associated with this wrapper.
*/
//外觀模式:對外暴露的容器外觀,用於傳遞容器中包含的ServletConfig參數而隱藏其他容器細節
protected final StandardWrapperFacade facade = new StandardWrapperFacade(this);
/**
* The (single) possibly uninitialized instance of this servlet.
*/
//線程間可見Servlet單例
protected volatile Servlet instance = null;
/**
* Flag that indicates if this instance has been initialized
*/
//標誌Servlet單例是否被成功初始化的標誌位
protected volatile boolean instanceInitialized = false;
/**
* The load-on-startup order value (negative value means load on
* first call) for this servlet.
*/
//
protected int loadOnStartup = -1;
/**
* Mappings associated with the wrapper.
*/
protected final ArrayList<String> mappings = new ArrayList<>();
/**
* The initialization parameters for this servlet, keyed by
* parameter name.
*/
//Servlet的初始化參數
protected HashMap<String, String> parameters = new HashMap<>();
/**
* The security role references for this servlet, keyed by role name
* used in the servlet. The corresponding value is the role name of
* the web application itself.
*/
//Servlet的安全角色引用,key爲servlet中使用的role name,value爲web應用自身的role name
protected HashMap<String, String> references = new HashMap<>();
/**
* The run-as identity for this servlet.
*/
//作爲***運行的標識
protected String runAs = null;
/**
* The notification sequence number.
*/
//JMX通知相關的序列號
protected long sequenceNumber = 0;
/**
* The fully qualified servlet class name for this servlet.
*/
//Servlet類的全限定名
protected String servletClass = null;
/**
* Does this servlet implement the SingleThreadModel interface?
*/
//線程間可見的Servlet是否支持singleThreadModel的標識
protected volatile boolean singleThreadModel = false;
/**
* Are we unloading our servlet instance at the moment?
*/
//線程間可見標識:當前是否正在卸載Servlet實例
protected volatile boolean unloading = false;
/**
* Maximum number of STM instances.
*/
//Servlet使用STM模式時,Servlet實例的最大數量
protected int maxInstances = 20;
/**
* Number of instances currently loaded for a STM servlet.
*/
//STM模式下:當前已經加載的Servlet實例數量
protected int nInstances = 0;
/**
* Stack containing the STM instances.
*/
//Servlet實例池,採用棧實現
protected Stack<Servlet> instancePool = null;
/**
* Wait time for servlet unload in ms.
*/
//等待Servlet卸載所需要的時間(ms)
protected long unloadDelay = 2000;
/**
* True if this StandardWrapper is for the JspServlet
*/
//是否爲JSPServlet
protected boolean isJspServlet;
/**
* The ObjectName of the JSP monitoring mbean
*/
//JSP監控的MBean
protected ObjectName jspMonitorON;
/**
* Should we swallow System.out
*/
//是否覆蓋System.out
protected boolean swallowOutput = false;
// To support jmx attributes
//標準容器閥
protected StandardWrapperValve swValve;
//Servlet實例加載時間
protected long loadTime=0;
//Servlet類加載時間
protected int classLoadTime=0;
/**
* Multipart config
*/
//多部分配置元素實例
protected MultipartConfigElement multipartConfigElement = null;
/**
* Async support
*/
//是否支持異步
protected boolean asyncSupported = false;
/**
* Enabled
*/
//容器是否可用
protected boolean enabled = true;
//是否要求掃描Servlet的安全註解
protected volatile boolean servletSecurityAnnotationScanRequired = false;
//是否可被重寫
private boolean overridable = false;
/**
* Static class array used when the SecurityManager is turned on and
* <code>Servlet.init</code> is invoked.
*/
//當SecurityManager開啓且Servlet.init方法調用時會用到的靜態class數組
protected static Class<?>[] classType = new Class[]{ServletConfig.class};
//三個讀寫鎖:當且僅當只有讀鎖時可重入
//用於控制參數的讀與寫線程安全
private final ReentrantReadWriteLock parametersLock =
new ReentrantReadWriteLock();
//用於控制mappings的讀與寫線程安全
private final ReentrantReadWriteLock mappingsLock =
new ReentrantReadWriteLock();
//用於控制references的讀與寫線程安全
private final ReentrantReadWriteLock referencesLock =
new ReentrantReadWriteLock();
看完成員變量後產生了以下疑惑:
a)available的值是如何確定的?
b)StandardWrapperFacade對象是用來做什麼的?
c)loadOnStartup的作用如何體現?
d)mappings裏面存放的是什麼?
e)references裏面存放的鍵值對的鍵與值意義相同嗎,爲什麼要這樣設計?
f)容器對象爲什麼採用Stack而不是ArrayList或其他集合類?
g)讀寫鎖的使用要解決什麼問題?
先讓這些疑惑暫時留存一段時間,待讀完方法代碼後再來嘗試回答。
(2)重要方法
StandardWrapper類中重要的方法主要有如下幾個,還是採用源碼加註釋的方式進行分析:
**a)loadServlet:**不可重入方法,如果一個實例也沒有,則加載並初始化一個Servlet實例,返回實例對象
/**
* Load and initialize an instance of this servlet, if there is not already
* at least one initialized instance. This can be used, for example, to
* load servlets that are marked in the deployment descriptor to be loaded
* at server startup time.
* @return the loaded Servlet instance
* @throws ServletException for a Servlet load error
*/
public synchronized Servlet loadServlet() throws ServletException {
// Nothing to do if we already have an instance or an instance pool
//如果非STM模式且實例已存在,直接返回
if (!singleThreadModel && (instance != null))
return instance;
PrintStream out = System.out;
if (swallowOutput) {
SystemLogHandler.startCapture();
}
Servlet servlet;
try {
long t1=System.currentTimeMillis();
// Complain if no servlet class has been specified
//如果Servlet類沒指定或指定錯誤則將該Servlet標記爲不可用
if (servletClass == null) {
unavailable(null);
throw new ServletException
(sm.getString("standardWrapper.notClass", getName()));
}
//通過一個實例工廠來做實例化操作(可通過反射等多種方式創建實例)
InstanceManager instanceManager = ((StandardContext)getParent()).getInstanceManager();
try {
//創建Servlet實例
servlet = (Servlet) instanceManager.newInstance(servletClass);
} catch (ClassCastException e) {
unavailable(null);
// Restore the context ClassLoader
throw new ServletException
(sm.getString("standardWrapper.notServlet", servletClass), e);
} catch (Throwable e) {
e = ExceptionUtils.unwrapInvocationTargetException(e);
ExceptionUtils.handleThrowable(e);
unavailable(null);
// Added extra log statement for Bugzilla 36630:
// http://bz.apache.org/bugzilla/show_bug.cgi?id=36630
if(log.isDebugEnabled()) {
log.debug(sm.getString("standardWrapper.instantiate", servletClass), e);
}
// Restore the context ClassLoader
throw new ServletException
(sm.getString("standardWrapper.instantiate", servletClass), e);
}
if (multipartConfigElement == null) {
MultipartConfig annotation =
servlet.getClass().getAnnotation(MultipartConfig.class);
if (annotation != null) {
multipartConfigElement =
new MultipartConfigElement(annotation);
}
}
processServletSecurityAnnotation(servlet.getClass());
// Special handling for ContainerServlet instances
// Note: The InstanceManager checks if the application is permitted
// to load ContainerServlets
if (servlet instanceof ContainerServlet) {
((ContainerServlet) servlet).setWrapper(this);
}
//記錄通過類創建實例的時間
classLoadTime=(int) (System.currentTimeMillis() -t1);
//在第一個實例加載完成之後,就能夠確定是否使用STM了
if (servlet instanceof SingleThreadModel) {
if (instancePool == null) {
//創建實例池
instancePool = new Stack<>();
}
//設置STM模式
singleThreadModel = true;
}
//初始化servlet
initServlet(servlet);
fireContainerEvent("load", this);
loadTime=System.currentTimeMillis() -t1;
} finally {
if (swallowOutput) {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
if (getServletContext() != null) {
getServletContext().log(log);
} else {
out.println(log);
}
}
}
}
return servlet;
}
b)allocate
/**
* Allocate an initialized instance of this Servlet that is ready to have
* its <code>service()</code> method called. If the servlet class does
* not implement <code>SingleThreadModel</code>, the (only) initialized
* instance may be returned immediately. If the servlet class implements
* <code>SingleThreadModel</code>, the Wrapper implementation must ensure
* that this instance is not allocated again until it is deallocated by a
* call to <code>deallocate()</code>.
*
* @exception ServletException if the servlet init() method threw
* an exception
* @exception ServletException if a loading error occurs
*/
@Override
public Servlet allocate() throws ServletException {
// If we are currently unloading this servlet, throw an exception
//如果正在卸載Servlet則不再分配實例
if (unloading) {
throw new ServletException(sm.getString("standardWrapper.unloading", getName()));
}
boolean newInstance = false;
// If not SingleThreadedModel, return the same instance every time
//如果不採用STM,每次返回的都是一個單例
if (!singleThreadModel) {
// Load and initialize our instance if necessary
//使用雙檢鎖(鎖的是容器對象)的方式創建Servlet單例,避免多線程創建出多個
if (instance == null || !instanceInitialized) {
synchronized (this) {
if (instance == null) {
try {
if (log.isDebugEnabled()) {
log.debug("Allocating non-STM instance");
}
// Note: We don't know if the Servlet implements
// SingleThreadModel until we have loaded it.
//注意:只有將Servlet類加載了之後才能知道它是否實現了SingleThreadModel接口
instance = loadServlet();
newInstance = true;
//實例加載完成後,singleThreadModel就是真正的值了,所以再次進行判斷
if (!singleThreadModel) {
// For non-STM, increment here to prevent a race
// condition with unload. Bug 43683, test case
// #3
//已經分配的實例數量++
countAllocated.incrementAndGet();
}
} catch (ServletException e) {
throw e;
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("standardWrapper.allocate"), e);
}
}
//有可能第一個線程將servlet創建出來了,但是還沒來得及初始化,第二個線程跑到這裏了,就先給它初始化一下
if (!instanceInitialized) {
initServlet(instance);
}
}
}
//STM模式下
if (singleThreadModel) {
//如果本次操作確實創建了一個新的實例出來
if (newInstance) {
// Have to do this outside of the sync above to prevent a
// possible deadlock
synchronized (instancePool) {
//將實例添加到池中
instancePool.push(instance);
nInstances++;
}
}
} else {
//非STM模式的Servlet把這一段else代碼運行完後就返回了,不可能再往後走
if (log.isTraceEnabled()) {
log.trace(" Returning non-STM instance");
}
// For new instances, count will have been incremented at the
// time of creation
//如果本次操作沒有新創建出實例,則分配數量++
//若創建出了新的實例,則在創建時已經計過數了
if (!newInstance) {
countAllocated.incrementAndGet();
}
return instance;
}
}
//只有STM模式下能夠運行到這裏來!!!
//鎖定實例池
synchronized (instancePool) {
//如果請求分配的實例數量大於已有的數量
while (countAllocated.get() >= nInstances) {
// Allocate a new instance if possible, or else wait
//在實例數量未達到最大數量時,嘗試新創建並加入到池中
if (nInstances < maxInstances) {
try {
instancePool.push(loadServlet());
nInstances++;
} catch (ServletException e) {
throw e;
} catch (Throwable e) {
ExceptionUtils.handleThrowable(e);
throw new ServletException(sm.getString("standardWrapper.allocate"), e);
}
} else {
try {
//否則就先等別人釋放使用完的Servlet實例回收到池中
instancePool.wait();
} catch (InterruptedException e) {
// Ignore
}
}
}
if (log.isTraceEnabled()) {
log.trace(" Returning allocated STM instance");
}
//已分配實例++
countAllocated.incrementAndGet();
//從池中彈出一個實例分配出去
return instancePool.pop();
}
}
**c)deallocate:**回收使用完的Servlet實例
/**
* Return this previously allocated servlet to the pool of available
* instances. If this servlet class does not implement SingleThreadModel,
* no action is actually required.
* @param servlet The servlet to be returned
* @exception ServletException if a deallocation error occurs
*/
@Override
public void deallocate(Servlet servlet) throws ServletException {
// If not SingleThreadModel, no action is required
if (!singleThreadModel) {
//非STM模式只改變分配的實例數量即可
countAllocated.decrementAndGet();
return;
}
// Unlock and free this instance
//STM模式下與allocate函數競爭instancePool對象鎖,不能同時使用池進行分配與回收
synchronized (instancePool) {
//已分配實例計數--
countAllocated.decrementAndGet();
//將實例重新放入到池中
//其實我覺得這裏似乎缺一個servlet的recycle操作,或許不進行狀態重置就是作者的本意吧,只是使用者需要了解在這裏沒有做servlet的狀態重置
instancePool.push(servlet);
//喚醒allocate函數中等待分配的線程
instancePool.notify();
}
}
**d)unload:**卸載所有初始化過的Servlet實例
/**
* Unload all initialized instances of this servlet, after calling the
* <code>destroy()</code> method for each instance. This can be used,
* for example, prior to shutting down the entire servlet engine, or
* prior to reloading all of the classes from the Loader associated with
* our Loader's repository.
*
* @exception ServletException if an exception is thrown by the
* destroy() method
*/
@Override
public synchronized void unload() throws ServletException {
// Nothing to do if we have never loaded the instance
//從來沒加載過就不必卸載
if (!singleThreadModel && (instance == null))
return;
//設置卸載狀態
unloading = true;
// Loaf a while if the current instance is allocated
// (possibly more than once if non-STM)
//如果當前實例正在活躍則等一會兒再卸載
//注意countAllocated只有在deallocate方法中才會--,所以這裏其實是在等待實例被回收,countAllocated置零
if (countAllocated.get() > 0) {
int nRetries = 0;
//計算出卸載每一個實例所需要暫停的平均時間
long delay = unloadDelay / 20;
//就這樣等待最多20個循環,啥也不幹,就等可能還在活躍的實例回收完成
while ((nRetries < 21) && (countAllocated.get() > 0)) {
if ((nRetries % 10) == 0) {
log.info(sm.getString("standardWrapper.waiting",
countAllocated.toString(),
getName()));
}
try {
Thread.sleep(delay);
} catch (InterruptedException e) {
// Ignore
}
nRetries++;
}
}
if (instanceInitialized) {
PrintStream out = System.out;
if (swallowOutput) {
SystemLogHandler.startCapture();
}
// Call the servlet destroy() method
try {
if( Globals.IS_SECURITY_ENABLED) {
try {
SecurityUtil.doAsPrivilege("destroy", instance);
} finally {
SecurityUtil.remove(instance);
}
} else {
//實例destroy
instance.destroy();
}
} catch (Throwable t) {
t = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(t);
instance = null;
instancePool = null;
nInstances = 0;
fireContainerEvent("unload", this);
unloading = false;
throw new ServletException
(sm.getString("standardWrapper.destroyException", getName()),
t);
} finally {
// Annotation processing
if (!((Context) getParent()).getIgnoreAnnotations()) {
try {
((Context)getParent()).getInstanceManager().destroyInstance(instance);
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
log.error(sm.getString("standardWrapper.destroyInstance", getName()), t);
}
}
// Write captured output
if (swallowOutput) {
String log = SystemLogHandler.stopCapture();
if (log != null && log.length() > 0) {
if (getServletContext() != null) {
getServletContext().log(log);
} else {
out.println(log);
}
}
}
}
}
// Deregister the destroyed instance
instance = null;
instanceInitialized = false;
if (isJspServlet && jspMonitorON != null ) {
Registry.getRegistry(null, null).unregisterComponent(jspMonitorON);
}
if (singleThreadModel && (instancePool != null)) {
try {
while (!instancePool.isEmpty()) {
//把實例池中實例依次彈出並destroy掉
Servlet s = instancePool.pop();
if (Globals.IS_SECURITY_ENABLED) {
try {
SecurityUtil.doAsPrivilege("destroy", s);
} finally {
SecurityUtil.remove(s);
}
} else {
s.destroy();
}
// Annotation processing
if (!((Context) getParent()).getIgnoreAnnotations()) {
((StandardContext)getParent()).getInstanceManager().destroyInstance(s);
}
}
} catch (Throwable t) {
t = ExceptionUtils.unwrapInvocationTargetException(t);
ExceptionUtils.handleThrowable(t);
instancePool = null;
nInstances = 0;
unloading = false;
fireContainerEvent("unload", this);
throw new ServletException
(sm.getString("standardWrapper.destroyException",
getName()), t);
}
instancePool = null;
nInstances = 0;
}
singleThreadModel = false;
unloading = false;
fireContainerEvent("unload", this);
}
4. 分析完代碼後來嘗試回答之前提出的問題
a)available的值是如何確定的?
首先,從StandardWrapper源碼可以知道,avaliable的值都是通過setAvailable方法進行設置的,而該方法又只在unavailable方法中調用,代碼如下:
@Override
public void unavailable(UnavailableException unavailable) {
getServletContext().log(sm.getString("standardWrapper.unavailable", getName()));
if (unavailable == null)
setAvailable(Long.MAX_VALUE);
else if (unavailable.isPermanent())
setAvailable(Long.MAX_VALUE);
else {
int unavailableSeconds = unavailable.getUnavailableSeconds();
if (unavailableSeconds <= 0)
unavailableSeconds = 60; // Arbitrary default
setAvailable(System.currentTimeMillis() +
(unavailableSeconds * 1000L));
}
}
觀察其餘代碼可以發現,大部分情況下傳入的參數是null,也就是說available通常會被設置爲Long.MAX_VALUE,若它要被設置爲數值,則數值來源於UnavailableException對象,找到其代碼位置,分析帶時間參數的初始化方法引用如下:
該方法已不被推薦使用,且找不到引用,因此可得出結論:available的數值要麼爲0,要麼爲Long.MAX_VALUE;
b)StandardWrapperFacade對象是用來做什麼的?
外觀模式的體現:傳遞ServletConfig參數的同時隱藏StandardWrapper容器的其他內部細節;
c)loadOnStartup的作用如何體現?
在StandardContext類中體現,主要用於對容器進行排序(TreeMap),確定啓動時容器的加載先後順序,代碼如下:
//StandardContext.java
/**
* Load and initialize all servlets marked "load on startup" in the
* web application deployment descriptor.
*
* @param children Array of wrappers for all currently defined
* servlets (including those not declared load on startup)
* @return <code>true</code> if load on startup was considered successful
*/
public boolean loadOnStartup(Container children[]) {
// Collect "load on startup" servlets that need to be initialized
TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
for (int i = 0; i < children.length; i++) {
Wrapper wrapper = (Wrapper) children[i];
int loadOnStartup = wrapper.getLoadOnStartup();
if (loadOnStartup < 0)
continue;
Integer key = Integer.valueOf(loadOnStartup);
ArrayList<Wrapper> list = map.get(key);
if (list == null) {
list = new ArrayList<>();
map.put(key, list);
}
list.add(wrapper);
}
// Load the collected "load on startup" servlets
for (ArrayList<Wrapper> list : map.values()) {
for (Wrapper wrapper : list) {
try {
wrapper.load();
} catch (ServletException e) {
getLogger().error(sm.getString("standardContext.loadOnStartup.loadException",
getName(), wrapper.getName()), StandardWrapper.getRootCause(e));
// NOTE: load errors (including a servlet that throws
// UnavailableException from the init() method) are NOT
// fatal to application startup
// unless failCtxIfServletStartFails="true" is specified
if(getComputedFailCtxIfServletStartFails()) {
return false;
}
}
}
}
return true;
}
d)mappings裏面存放的是什麼?
只找到addMapping/removeMapping/findMappings這三個方法,引用情況如下圖所示,猜測是用來保存一些與容器關聯的額外數據吧,需要注意的是使用了讀寫鎖保證安全性同時提高吞吐量;
e)references裏面存放的鍵值對的鍵與值意義相同嗎,爲什麼要這樣設計?
與mappings類似,但是引用的位置不同,應該是主要用於做安全相關的處理;
f)容器對象爲什麼採用Stack而不是ArrayList或其他集合類?
Stack的特性是後進先出,可以使得下一次分配時分配的實例是最近被回收的,也就是說棧底的實例被分配出去的頻率可能會非常低,其實我覺得采用隊列也是可以的,這裏作者的設計意圖還沒有理解得特別明白,待後續查閱更多資料後再來補充;
g)讀寫鎖的使用要解決什麼問題?
併發讀,同步寫,提高吞吐量;
5. 分析源碼時的額外收穫
分析源碼時會打很多斷點,跟蹤很多次程序的運行,因爲代碼很多都是相互關聯的,所以在分析的過程中也會接觸到除分析目標以外的一些知識,將其分別記錄如下:
(1)ReentrantReadWriteLock的最大加鎖層級:讀/寫鎖都是65535;
三、後記
優秀的源碼博大精深,加上自己水平有限,所以若文中有錯誤、遺漏,請各位讀者不吝指出,先行感謝;
項目、源碼分析系列將持續更新,歡迎關注。