标签:
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