1.Synchronized的实现原理
同步代码块使用monitorenter
和monitorexit
两个指令实现,可以把执行monitorenter
指令理解为加锁,执行monitorexit
理解为释放锁。 每个对象维护着一个记录着被锁次数的计数器。未被锁定的对象的该计数器为0,当一个线程获得锁(执monitorenter
)后,该计数器自增变为 1 ,当同一个线程再次获得该对象的锁的时候,计数器再次自增。当同一个线程释放锁(执行monitorexit
指令)的时候,计数器再自减。当计数器为0的时候。锁将被释放,其他线程便可以获得锁。
每个对象自身维护这一个被加锁次数的计数器,当计数器数字为0时表示可以被任意线程获得锁。当计数器不为0时,只有获得锁的线程才能再次获得锁。即可重入锁。
原子性的保证:通过monitorenter
和monitorexit
指令,可以保证被synchronized
修饰的代码在同一时间只能被一个线程访问,在锁未释放之前,无法被其他线程访问到。
可见性的保证:synchornized保证在对一个变量解锁之前,将这个变量刷新到主存。
Synchronized是无法保证有序性的
在JIT编译过程中,虚拟机会根据情况使用这三种技术对锁进行优化,目的是减少锁的竞争,提升性能。
锁的优化
1.自旋锁:
2.锁消除:
//因为以下代码中hollis是一个局部变量,不会有其他线程来访问的
//所以是没必要加锁的,所以在JIT编译阶段就会被优化掉
public void f() {
Object object = new Object();
synchronized(object) {
System.out.println(object);
}
}
//优化之后的代码是
public void f() {
Object object= new Object();
System.out.println(object);
}
3.锁粗化:
一般情况下,我们将的都是锁细化,就是不要把多余的操作放到同步代码中,锁的内部只写与并发有关的内容,这样有助于提高效率。大部分情况是如此。但是有种情况比较特殊:如果一连续操作都是对同一对象加锁解锁,甚至加锁操作出现在循环体中,JIT会将加锁的范围拓展到外围, 这其实和我们要求的减小锁粒度并不冲突。减小锁粒度强调的是不要在银行柜台前做准备工作以及和办理业务无关的事情。而锁粗化建议的是,同一个人,要办理多个业务的时候,可以在同一个窗口一次性办完,而不是多次取号多次办理。
for(int i=0;i<100000;i++){
synchronized(this){
do();
}
//锁粗化之后
synchronized(this){
for(int i=0;i<100000;i++){
do();
}