文章目录
- 1. 设置 final 变量的原理
- 2. 获取 final 变量的原理
1. 设置 final 变量的原理
理解了 volatile 原理,再对比 final 的实现就比较简单了
public class TestFinal {
final int a = 20;
}
字节码
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 20
7: putfield #2 // Field a:I
<-- 写屏障
10: return
发现 final 变量的赋值也会通过 putfield 指令来完成,同样在这条指令之后也会加入写屏障,保证在其它线程读到它的值时不会出现为 0 的情况
写屏障可以保证两点:
- 可以保证写屏障之前的指令不会被重排序到写屏障后面去
- 保证写屏障之前的修改操作,赋值操作,在写屏障之后可以同步到主存,对其他线程可见
2. 获取 final 变量的原理
public class TestFinal {
final static int A = 10;
final static int B = Short.MAX_VALUE+1;
final int a = 20;
final int b = Integer.MAX_VALUE;
final void test1() {
final int c = 30;
new Thread(()->{
System.out.println(c);
}).start();
final int d = 30;
class Task implements Runnable {
@Override
public void run() {
System.out.println(d);
}
}
new Thread(new Task()).start();
}
}
class UseFinal1 {
public void test() {
System.out.println(TestFinal.A);
System.out.println(TestFinal.B);
System.out.println(new TestFinal().a);
System.out.println(new TestFinal().b);
new TestFinal().test1();
}
}
class UseFinal2 {
public void test() {
System.out.println(TestFinal.A);
}
}
从字节码中才能看出来,
在这个测试中,如果TestFinal中的A加了final,那么类UseFinal1中使用A的时候就发现,TestFinal直接把A复制了一份放在了UseFinal1的栈中,如果不加final,那么UseFinal1使用A的时候要调用GETSTATIC方法去获取。
同理TestFinal中的B,虽然是个很大的数,但是加了final,UseFinal1在使用的时候,发现是从常量池中取得(就是复制到常量池中了),要是不加final,UseFinal1使用的时候,还是要GETSTATIC。
这就是读取final变量时的优化。简单地说,其实就是相当于复制一份直接给用的那个人。