关于前一篇的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上。