1:对象头
首先了解下32bit的Java对象头如下:
通过上面的表格,我们可以看出,synchronized用的锁是锁在Java对象头中的,Java的每个对象都可以作为锁,具体表现为下面三种方式:
a:对于普通的方法,锁是当前的实例
b:对于静态方法,锁是当前类的Class对象
c:对于同步方法块,锁是Synchronized括号中配置的对象
从JVM规范中看出,同步代码块和同步方法,在实现的细节上有所不同,同步代码块是通过monitorEnter和monitorExit指令来实现的,同步方法在虚拟机规范规范中没有详细的说明,当线程执行到monitorEnter的时候,将尝试获取当前对象所对应的monitor所有权,即尝试获取对象的锁。
下面讲讲我对锁的理解:
偏向锁:
a:偏向锁的获取
大多数情况下,锁不存在多线程的竞争,而总是由同一个线程获取,为了让线程获取到锁的代价更低而引入了偏向锁,当一个线程需要访问同步代码的时候,会在栈桢中存储当前线程的线程ID,以后该线程进入和退出同步代码块的时候,不需要通过CAS进行加锁和解锁,只需要简单的检查一下栈桢中保存的线程ID是否和当前运行的线程ID一样,如果测试成功,表明线程已经获取到了锁,如果测试失败,表明有竞争,当到达安全点的时候获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行代码。
b:偏向锁的释放
偏向锁只用遇到其他线程尝试竞争偏向锁的时候,持用偏向锁的线程才会释放锁,线程不会主动去释放偏向锁,偏向锁的释放,需要等到全局安全点(这个时间点上没有字节码正在执行),他会首先暂停拥有偏向锁的线程,判断锁是否是处于被锁定的状态,撤销偏向锁后恢复到未锁定的状态或轻量级的状态。
c:关闭偏向锁
偏向锁在Java6和Java7中是默认开启的,但是他在程序启动几秒钟后才激活,如果有必要,可以通过-XX:BiasedLockingStartupDelay=0来关闭,如果确定应用程序的锁通常处于竞争状态,可以通过JVM参数关闭偏向锁,-XX:UseBiasedLocking=false。那么程序会默认进入轻量级锁状态。
优缺点:
优点是加锁和解锁不需要额外的消耗,和执行非同步代码块所用的时间相差很少。缺点是如果线程间存在锁的竞争,就会带来额外的消耗,使用于一个线程访问的场景。
轻量级锁:
a:轻量级锁加锁
在线程执行到当前的同步代码的时候,JVM会现在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的Mark Work复制到锁记录中,广发称之为Displaced Mark Word,然后尝试使用CAS将对象头中的Mark Word指向锁记录的指针,如果成功,表示当前线程获取锁,如果获取失败,表明其他线程竞争锁,当前线程尝试使用自旋来获取锁。
b:轻量级锁的解锁
轻量级锁解锁的时候,会使用原子的CAS操作将Displaced Mark Word替换回对象头,如果成功,表示没有竞争,如果失败,表明当前锁存在竞争,锁就会膨胀成重量级锁。
优缺点:
优点是竞争的线程不会阻塞,提高线程的响应的速度,缺点是如果获取不到锁,使用自旋会消耗CPU,适用与多个线程交替进入同步块的场景。
重量级锁:
Java的线程是映射到操作系统原生的线程之上的,如果要阻塞或者唤醒一个线程就需要操作系统的帮忙,这就要从用户态转换到核心态,因此需要花费更多的处理器的时间,对于简单的代码,可能状态的转换消耗的时间比用户执行代码所需要的时间还要长,所以说synchronized是Java语言的一个重量级锁。
优缺点:
优点是线程不使用自旋,不会消耗CPU,缺点是线程阻塞,响应时间慢。适用于多个线程同时进入同步代码块的场景。