线程的五种状态:
在程序中,Java虚拟机的开发工程师们在分析过大量数据后发现,共享数据的锁定状态一般只会持续很短的一段时间,为了这段时间去挂起和恢复线程其实并不值得。
如果物理机上有多个处理器,可以让多个线程同时执行的话,就可以让后来的线程“稍微(忙)等一下”,不会放弃处理器事件,看看持有锁的线程会不会很快释放锁。这个“稍微等一下”的过程就是自旋。
在动态编译同步块的时候,JIT编译器可以借助一种叫逃逸分析(Escape Analysis)的技术来判断同步块所使用的锁对象是否只能够被一个线程访问而没有被发布到其他线程。
如果同步块所使用的锁对象通过这种分析被证实只能被一个线程访问,那么JIT编译器在编译这个同步块的时候就会取消对这部分代码的同步。
比如以下代码:
public void f() { Object hollis = new Object(); synchronized(hollis) { System.out.println(hollis); } }
明显hollis的并不会被其他线程访问,所以JIT在编译时就会优化掉:
public void f() { Object hollis = new Object(); System.out.println(hollis); }
通常情况下,我们提倡尽量减小锁的粒度,把无关的准备工作放到锁外面,锁内部只处理和并发相关的内容,这样可以避免不必要的阻塞。
但是有些时候,在一段代码中连续对同一个对象反复加锁解锁,其实是相对耗费资源的,这时候可以恰当放宽加锁的范围,减少性能损耗。
JIT发现一系列连续的操作都对同一个对象反复加解锁,甚至加锁出现在循环体中时,会将加锁同步的方位扩散(粗化)到整个操作序列的外部。
如:
for(int i=0;i<100000;i++){ synchronized(this){ do(); }
会被粗化为:
synchronized(this){ for(int i=0;i<100000;i++){ do(); }