Java并发机制的底层实现原理 一
Java并发机制的底层实现原理中我们今天看下几个简单的并发锁:
目的:为了减少加锁和释放锁带来的开销,Java SE 1.6 提出来两个概念:偏向锁 和 轻量级锁。
锁的4种状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态
一、偏向锁
经过研究发现,一个锁通常是会被最近使用的线程继续得到使用,所以在Java SE 1.6版本中,Java的对象头中添加了一个锁标志位,用于标识这个锁是不是偏向锁。线程访问同步锁的时候先检查对象头和栈帧中书否有该线程的ID,有的话直接访问该同步资源,不用进行CAS竞争锁资源。若不包含在那么线程先得到锁之前先检查下是不是偏向锁状态,是:就尝试使用CAS将对象头的偏向锁,指向将要访问的线程ID。如果没有设置偏向锁标识,那么该线程就使用CAS进行锁资源的竞争,获得锁之后再讲对象头的偏向锁指向该线程ID。
1.偏向锁的撤销
偏向锁使用一种等到竞争才释放锁的机制,所以当其他线程竞争偏向锁的时候,持有偏向锁的线程才会释放锁。偏向锁的释放,首先会停止持有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处理存活状态则将对象头设置为无锁状态。如果当前持有锁的线程还是存活的,那么就先执行拥有偏向锁的栈,设置对象头为无锁转态,在唤醒持有锁的线程继续执行。
2.偏向锁的关闭
在Java SE 1.6 以后的版本中默认偏向锁都是开启的,在应用程序启动之后就会立刻开启,也可以在JVM中设置延时开启:-XX:BiasedLokingStartupDelay=0,要是我们可以明确的知道我们的程序中一定大多数情况下从在锁竞争那么我们可以关闭偏向锁锁,那我们的应用就变成了轻量级锁:-XX:-UseBasicLoking=false;
二、轻量级锁
1.轻量级锁加锁
当偏向锁进入到轻量级锁状态的时候,线程在执行同步代码块的时候,JVM先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头中的mark word 复制到锁记录中,然后线程尝试使用CAS自旋将对象头中的Mark Word替换为指向锁记录的指针,若果成功那么该线程获得锁,如果失败则表示其他线程在竞争该锁,当前线程使用自旋获取锁。
2.轻量级锁解锁
轻量级锁解锁,会使用CAS操作将复制出来的Displaced Mark Word 先替换回对象头中,若果成功则轻量级锁释放成功,但是失败的话,表示当前锁有竞争,锁就会膨胀成为重量级锁。这样会释放掉锁在唤醒其他的线程。
因为自旋会消耗CPU为了避免无用的自旋(比如线程获得锁被阻塞),一旦锁升级为重量级锁那么就不会在降级为轻量级锁,等到线程释放了锁之后在唤醒其他的线程竞争锁。
接下来看一下几种锁之间的优缺点:
1.优点
偏向锁:加锁和解锁不需要额外的消耗,几乎与执行非同步方法一样
轻量级锁:竞争的线程不会阻塞,提高线程的响应速度。
重量级锁:线程竞争不会使用CAS自旋,不消耗CPU
2、缺点
偏向锁:如果线程之间存在竞争会带来锁撤销的开销
轻量级锁:如果始终得不到的锁竞争的线程会自旋CAS消耗CPU
重量级锁:线程阻塞,响应时间慢
3、使用场景
偏向锁:适用于一个线程访问同步代码块
轻量级锁:追求响应时间,同步代码块执行快
重量级锁:追请吞吐量,同步代码执行时间较长。