Posted on 2009-10-06 23:56
dennis 閱讀(3514)
評論(0) 編輯 收藏 所屬分類:
java 、
my open-source
上篇文章我談到了
java nio的一個嚴重BUG,并且介紹了jetty是如何規避這個BUG的。我在將這部分代碼整合進yanf4j的過程中發現了不少誤判的情況,讓我們看看誤判是怎么發生的。jetty的解決方案是通過在select返回為0的情況下,計量Selector.select(timeout)執行的時間是否與傳入的timeout參數相差太大(小于timeout的一半),如果相差太大,那么認為發生一次bug,如果發生的次數超過設定值,依據嚴重程度進行處理:第一嘗試取消任何有效并且interestOps等于0的SelectionKey;第二次就是重新創建一個Selector,并將有效的Channel注冊到新的Selector。誤判的發生就產生于這個時間的計量上,看javadoc可以發現它是這樣描述這個方法的:
This method performs a blocking
selection operation. It
returns only after at least one channel is selected, this selector's
wakeup
method is invoked, or the current thread is interrupted, whichever comes first
意思就是說這個方法將阻塞select調用,直到下列三種情況之一發生才返回:至少一個channel被選中;同一個Selector的wakeup方法被調用;或者調用所處的當前線程被中斷。這三種情況無論誰先發生,都將導致select(timeout)返回。因此為了減少誤判,你需要將這三種情況加入判斷條件。Jetty的方案已經將select返回為0的情況考慮了,但是卻沒有考慮線程被中斷或者Selector被wakeup的情況,在jetty的運行時也許不會有這兩種情況的發生,不過我在windows上用jdk 6u7跑jetty的時候就發現了誤判的日志產生。除了wakeup和線程中斷這兩種情形外,為了進一步提高判斷效率,應該將操作系統版本和jdk版本考慮進來,如果是非linux系統直接不進行后續的判斷,如果是jdk6u4以后版本也直接忽略判斷,因此yanf4j里的實現大致如下:
boolean seeing = false;
/**
* 非linux系統或者超過java6u4版本,直接返回
*/
if (!SystemUtils.isLinuxPlatform()
|| SystemUtils.isAfterJava6u4Version()) {
return seeing;
}
/**
* 判斷是否發生BUG的要素:
* (1)select返回為0
* (2)wait時間大于0
* (3)select耗時小于一定值
* (4)非wakeup喚醒
* (5)非線程中斷引起
*/
if (JVMBUG_THRESHHOLD > 0 && selected == 0
&& wait > JVMBUG_THRESHHOLD && now - before < wait / 4
&& !this.wakenUp.get() /* waken up */
&& !Thread.currentThread().isInterrupted()/* Interrupted */) {
this.jvmBug.incrementAndGet();
其中判斷是否是線程中斷引起的是通過Thread.currentThread().isInterrupted(),判斷是否是wakeup是通過一個原子變量wakenUp,當調調用Selector.wakeup時候,這個原子變量更新為true。判斷操作系統和jdk版本是通過System.getProperty得到系統屬性做字符串處理即可。類似的代碼示例:
public static final String OS_NAME = System.getProperty("os.name");
private static boolean isLinuxPlatform = false;
static {
if (OS_NAME != null && OS_NAME.toLowerCase().indexOf("linux") >= 0) {
isLinuxPlatform = true;
}
}
public static final String JAVA_VERSION = System
.getProperty("java.version");
private static boolean isAfterJava6u4Version = false;
static {
if (JAVA_VERSION != null) {
// java4 or java5
if (JAVA_VERSION.indexOf("1.4.") >= 0
|| JAVA_VERSION.indexOf("1.5.") >= 0)
isAfterJava6u4Version = false;
// if it is java6,check sub version
else if (JAVA_VERSION.indexOf("1.6.") >= 0) {
int index = JAVA_VERSION.indexOf("_");
if (index > 0) {
String subVersionStr = JAVA_VERSION.substring(index + 1);
if (subVersionStr != null && subVersionStr.length() > 0) {
try {
int subVersion = Integer.parseInt(subVersionStr);
if (subVersion >= 4)
isAfterJava6u4Version = true;
} catch (NumberFormatException e) {
}
}
}
// after java6
} else
isAfterJava6u4Version = true;
}
}