上篇文章我谈到了
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
; } }
文章转自庄周梦蝶 ,原文发布时间2009-10-06