一前记
针对公众号关注者针对Lock,synchronized和cmpxchg提出的疑问,这里特意补充一片来分析一下Lock和synchronized区别,以及cmpxchg的意义。
二Lock和synchronized的区别
我们在深挖锁上篇已经知道synchronized是由javac编译成moniterenter和moniterexit指令实现代码同步块的,也就是说synchronized的同步实现是由JVM对moniterenter与moniterexit指令的执行来实现的。而Lock类相关的锁实现是不依赖于JVM的虚拟机指令实现,而是直接通过java代码实现的。这里我们以重入锁ReetrantLock的lock方法的实现来说明:
static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; /** * Performs lock. Try immediate barge, backing up to normal * acquire on failure. */ final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } ...//省略 }
这里我们以ReentrantLock默认的非公平锁来进行分析,这里可以看到NonfairSync.lock()便是ReentrantLock.lock()的实现方法。ReentrantLock获取锁首先通过compareAndSetState()尝试将锁对象的state标志由期望值0设置为1,如果设置成功,则说明成功获取了锁,如果失败则通过调用同步队列的acquire()方法,将线程加入同步队列进行锁等待。这里compareAndSetState便是获取锁的关键实现,下面便是compareAndSetState的实现:
protected final boolean compareAndSetState(int expect, int update) { // See below for intrinsics setup to support this return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }
如果看过之前面“试题13解析-CAS与ABA”的同学应该对这个函数不陌生,正是CAS原子操作系列的函数,在处理器级别,CAS的原子性是由处理器对cmpxchg汇编指令加入LOCK_IF_MP前缀所提供的内存屏障进行保证的(深挖锁上篇),而synchronized对应的moniter虚拟机指令的也同样是由cmpxchg实现的。这里我们一定要理解原子性与锁是不同的概念,CAS为锁的加解锁操作提供了原子性操作的保证,但是CAS并不代表锁,CAS提供的是变量原子性操作的功能,例如Atomic包中的类的实现也是通过CAS来对变量进行原子性操作的。
其实,锁可以抽象成一个标记,线程尝试获取锁的过程,就是尝试将该标记设置为已上锁的过程,只要我们能保证只有一个线程可以成功设置该标记(即上锁过程的原子性),那么这个标记就是一把锁。Lock与synchronized(moniter指令)获取锁与释放锁过程中正是使用CAS来设置锁标志的。
Lock接口是JDK5之后提供的锁机制,其相对于关键字synchronized为程序员提供了更灵活的加锁解锁接口,以及公平锁非公平锁等更多的锁特性,本文意在解释原子性与锁的关系,为了避免陷入Lock实现的细节,因此这里就不展开说明Lock接口以及Lock实现依赖的同步队列AbstractQueuedSynchronizer的相关内容了。最后说一下区别synchronized是在JVM中实现的,而Lock的锁逻辑是Java层直接实现的。