码迷,mamicode.com
首页 > 其他好文 > 详细

final语义

时间:2015-08-26 20:39:24      阅读:257      评论:0      收藏:0      [点我收藏+]

标签:

场景1:

final ArrayList<String> list = new ArrayList<>();

此处将map定义为final可以保证map的构造参数执行完毕才会把其赋给引用,让我们重温一下new对象的作用:

首先《Java并发编程实战》有说,new的机器指令为10条左右,比malloc快,其次,除去new一个大对象的情况(new一个大对象有时候会直接分配到老年代),new一个对象的过程是:
1、取到eden的全局锁。
2、通过指针碰撞的方式来移动指针分配内存(这样成立的前提是每次minor GC之后Eden总是空得,当然还有一个survivor区也总是空的)。
3、执行构造函数。
当然还有另外一种更高效的方式,就是TLAB:
1、Sun Hotspot JVM为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间TLAB(Thread Local Allocation Buffer),其大小由JVM根据运行的情况计算而得,在TLAB上分配对象时不需要加锁,因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配,在这种情况下JVM中分配对象内存的性能和C基本是一样高效的,但如果对象过大的话则仍然是直接使用堆空间分配。(PS:在C中大部分结构体都是栈上分配,这也是为什么C如此神速)。
2、TLAB仅作用于新生代的Eden Space,因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效。
3、执行构造函数。
所以场景1体现了final的语义之一:
用final修饰引用就可以避免在对象构造不完全时返回引用。

场景2:

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这个方法可以放心的被优化。



final语义

标签:

原文地址:http://my.oschina.net/stillotherguy/blog/497457

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!