在刚接触jdk5的时候,网上都说能尽量不实用synchronize就不用,同时有替代方案,比如轻量级锁,locks,但是在jdk6之后对synchronized有了优化。
应用场景
既然引入了同步锁的概念,那啥时候可以用?当然是处在共享资源且有多个线程可能对其操作的情况。比如定义了一个全局变量,由多个线程对其进行+1操作,如果让不同的线程来执行,在操作时获取到的值可能是旧的,这就会导致最终的结果跟预期不一致,这时我们就需要保证其安全性。
既然说到保证安全性,那么肯定会对程序执行的效率产生影响,毕竟这是一个互斥的属性,synchronized在其中寻求折中点,所以出现了:无锁->偏向锁->轻量级锁->重量级锁。
synchronized实用的位置还是没有变,分别在:普通方法、静态方法、普通方法块中。
/**
* synchronized用法
*/
public class TestSyn {
public synchronized void get(){}
public void find(){
synchronized (this){
}
}
public static synchronized void put(){}
public void put2(){
synchronized (TestSyn.class){}
}
}
其中,1和2 、3和4具有相同的作用,如果是对于用.class作为锁,那么就会产生互斥作用,因为synchronized内部通过监视器(monitorenter、monitorexixt)指令进出锁,当使用.class,全局就只有一个,那么就会产生资源竞争。
所以在产生资源竞争时,又不好让未获得锁的线程直接阻塞,及节省后续的恢复线程切换上下文所需的资源开销,jdk引入了上述的4种锁类型。首先无锁肯定是效率最快的,平常我们工作使用的,大部分是这种被定义为private的属性。
升级流程
首先进入锁时,会调用cas方法,把mark word记录的线程id改成当前线程,且偏移锁标志位为1,epoch改成01,如果修改成功,则执行方法体内容。
如果当前有线程获取到偏移锁且线程未结束时,其他线程想通过CAS操作修改锁对象头的id失败时,锁会立刻进行升级操作,变成轻量级锁。如果其他线程调进入同步方法,且当前拥有偏移锁的线程isalive返回false时,cas操作会成功执行,此时则不会升级。
升级为轻量级锁时,有两种规则来获取锁,一种是自旋操作,另外一种是自适应自旋操作。其中自旋锁可以通过调整jvm参数preBlockSpin,默认次数是10。另外一种自适应自旋锁,是根据最近一次获得该对象锁耗费的自选次数来进行调整,可能多也可能少,如果没有成功获取过,可能会跳过该步骤。在当前阶段,默认的一种场景就是,获取锁操作能立刻返回。如果此时还没获取到锁, 那么会升级成重量级锁,进行阻塞,在当前占用锁释放资源后,再进行资源竞争,重量级锁使用monitor监视器来实现,借助monitorenter和monitorexixt指令对其他线程进行阻塞和唤醒