标签:
final ArrayList<String> list = new ArrayList<>();
此处将map定义为final可以保证map的构造参数执行完毕才会把其赋给引用,让我们重温一下new对象的作用:
首先《Java并发编程实战》有说,new的机器指令为10条左右,比malloc快,其次,除去new一个大对象的情况(new一个大对象有时候会直接分配到老年代),new一个对象的过程是:
class Foo {
public final String name;
public Foo(String name) {
this.name = name;
}
public static void main(String[] args) {
Foo foo = new Foo("Ethan");
}
}
此处的foo引用也可以保证拿到的是构造完全的对象,体验了final的另外一个语义:
在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。场景3:
class Foo {
public final String name;
public Foo(String name) {
this.name = name;
}
public String getName() {
return name;
}
public static void main(String[] args) {
Foo foo = new Foo("Ethan");
String name = foo.getName();
}
}
场景3体验了final的另外一个语义:
初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。
public class ReorderTest {
private static List<String> l = new ArrayList<>();
public static void main(String[] args) {
Thread[] threads = new Thread[20];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (ReorderTest.class) {
if(l.size() < 1024)
l.add("ttt");
}
}
}
});
threads[i].start();
}
Thread t1 = new Thread(){
@Override
public void run(){
while(true){
@SuppressWarnings("unused")
List<String> local = l;
l = new ArrayList<String>(1024);
}
}
};
t1.start();
}
}
运行这个例子会发现一个异常:
Exception in thread "Thread-5" java.lang.NullPointerException at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:234) at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227) at java.util.ArrayList.add(ArrayList.java:458) at com.rabbit.concurrent.ReorderTest$1.run(ReorderTest.java:19) at java.lang.Thread.run(Thread.java:745)
进入ArrayList源码:
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)//此处elementData为空发生异常
grow(minCapacity);
}
只要是构造完全的ArrayList,它的elementData肯定是不为null的,所以没有把它定义为final也算一个小小“缺陷”
final的别的好处:
变量折叠:
final int i = 1; final int j = 2; int k = i + j;
此处k的值可以在编译期确定为5
方法内联:
在JVM中有方法内联和JIT及时编译的优化,但是这些优化都会留一个回门,比如一个类的方法被内联了(优化方法调用开销),如果这个类的子类被加载,并且这个子类覆盖了被内联的方法,那么JVM就会取消优化,将方法声明为final可以更明确的告诉JVM这个方法可以放心的被优化。
标签:
原文地址:http://my.oschina.net/stillotherguy/blog/497457