cpu sy高case和static case(续)

关于前一篇的cpu sy high的那个case,问题的代码为:
[code]
private Semaphore sp = new Semaphore(1);

private AtomicBoolean readyFlag = new AtomicBoolean(true);

public long next(){
while (!this.readyFlag.get())
{
Thread.yield();
}

long m = this.max.get();
long rtn = this.idGenerator.next();

if (rtn > m) {
if (!this.readyFlag.get())
return next();
try
{
this.sp.acquire();
this.readyFlag.set(false);

do sth…

this.readyFlag.set(true);
}
catch (InterruptedException e) {
e.printStackTrace();
}
finally {
println(“released”);
this.sp.release();
}

return next();
}
return rtn;
}
[/code]

这段代码除了Thread.yield这个地方需要吐槽外,还有好几个地方要吐槽…

首先要吐槽的是目前这样的写法,如代码执行到do sth…那个部分,然后触发了InterruptedException,就会直接导致readyFlag永远为false,于是之后的代码只要调用到next(),就永远会陷入Thread.yield,cpu sy很快就会很high。

第二个要稍微吐槽的是检查readyFlag的那段代码在sp.acquire之以后也应该放下。

这段代码我写的话,会改造成类似如下:
[code]
private AtomicBoolean readyFlag = new AtomicBoolean(true);

private ReentrantLock lock = new ReentrantLock();

private Condition waitReadyFlag = lock.newCondition();

public long next(){
while(!readyFlag.get()){
try{
lock.lock();
if(!readyFlag.get())
waitReadyFlag.await();
}
catch(InterruptedException e){
// IGNORE
}
finally{
lock.unlock();
}
}

long m = this.max.get();
long rtn = this.idGenerator.next();

if (rtn > m) {
try{
lock.lock();
if(readyFlag.get()){
return next();
}
readyFlag.set(false);
// do sth…
}
finally{
if(!readyFlag.get()){
readyFlag.set(true);
waitReadyFlag.signalAll();
}
lock.unlock();
}
return next();
}

return rtn;
}
[/code]

————分隔线,再说另外一个话题————

类的load/link/initialization是Java中最为基础的知识,细节的信息在JVM规范中有描述,这里还是拿之前的那篇文章里的例子来继续说下关于static的初始化(有些时候还是挺容易写错)。

假设case的代码修改为如下:
[code]
public class A {

public static void main(String[] args) {
System.out.println(B.NICK);
System.out.println(B.NAME);
}

}

public class B{

public static final String NICK = “nick”;

public static final String NAME = getName();

private static String getName(){
return “bluedavy”;
}
static{
System.out.println(“hellojava”);
}
}
[/code]

上面这段代码执行的输出为:
nick
hellojava
bluedavy

不按规范里那么学术化的来解释上面的原因,感兴趣的可以对上面编译后的A、B两个类用javap -c -verbose -private来查看里面的具体信息,javap -c -verbose B后可以看到如下的关键信息:
[code]
public static final java.lang.String NICK;
Constant value: String nick

public static final java.lang.String NAME;

static {};
Code:
Stack=2, Locals=0, Args_size=0
0: invokestatic #14; //Method getName:()Ljava/lang/String;
3: putstatic #18; //Field NAME:Ljava/lang/String;
6: getstatic #20; //Field java/lang/System.out:Ljava/io/PrintStream;
9: ldc #26; //String hellojava
11: invokevirtual #28; //Method java/io/PrintStream.println:(Ljava/lang/St
ring;)V
14: return
[/code]
可以看到NICK后面标为Constant value,而NAME的赋值则放在了static block中,java在将源码编译为字节码时,会将static对应的赋值动作或源码中的多个static block(例如{…} static{…})按照顺序全部放到static block中,如用btrace在运行时要跟踪这个static block的执行,可跟踪方法,就类似构造器在运行时对应的方法名为
在编译为字节码后,对于B的代码而言,其实相当于变成了:
[code]
public class B{

public static final String NICK = “nick”;

public static final String NAME;

private static String getName(){
return “bluedavy”;
}
static{
NAME = getName();
System.out.println(“hellojava”);
}
}
[/code]

至于为什么在调用B.NICK的时候没触发static block的执行,javap看下A的字节码就懂了,查看时可看到如下关键信息:
[code]
public static void main(java.lang.String[]);
Code:
Stack=2, Locals=1, Args_size=1
0: getstatic #16; //Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #22; //String nick
5: invokevirtual #24; //Method java/io/PrintStream.println:(Ljava/lang/St
ring;)V
8: getstatic #16; //Field java/lang/System.out:Ljava/io/PrintStream;
11: getstatic #30; //Field com/bluedavy/spike/B.NAME:Ljava/lang/String
;
14: invokevirtual #24; //Method java/io/PrintStream.println:(Ljava/lang/St
ring;)V
17: return
[/code]

可看到System.out.println(B.NICK)这行相当于被直接改为了System.out.println(“nick”),仔细看过JVM规范的同学会知道对于constant类型的static final变量,在编译为字节码阶段就会直接被替换为对应的值,这里有个小小的坑是可能会碰到的,就是编译的时候只编译了对应的static final变量的代码,但没重编译相应引用的代码,那就悲催了,运行的时候还会是之前的值。

=============================
欢迎关注微信公众号:hellojavacases

关于此微信号:
分享Java问题排查的Case、Java业界的动态和新技术、Java的一些小知识点Test,以及和大家一起讨论一些Java问题或场景,这里只有Java细节的分享,没有大道理、大架构和大框架。

公众号上发布的消息都存放在http://hellojava.info上。