1、synchronized确保线程互斥访问同步代码,synchronized和volatile都保证了内存可见性,解决了重排序(为了充分利用多核、多级缓存)。
内存可见性:每个CPU都有自己的缓存区,其他CPU上的线程无法看到该CPU对变量的修改。
synchronized和ReentrantLock一样,也是可重入的。为什么需要可重入,看如下代码,如果synchronized不是可重入的,执行b.doSomething()时先获得b上的锁,super.doSometing()还要获得b上的锁,会产生死锁。
class A {
public void synchronized doSomething() {}
}
class B extends A {
public void synchronized doSomething() {
super.doSomething();
}
}
最初的synchronized使用监视器锁,是互斥锁、重量级锁,堆上的对象有对象头,记录了锁的状态。synchronized代码块是使用JVM的monitorenter和monitorexit指令实现的;synchronized方法在class里给方法的修饰符加上ACC_ SYNCHRONIZED标志,也是使用的监视器锁。
jdk1.6优化了内置锁synchronized:
(1)自旋锁(自适应自旋锁)
线程阻塞和唤醒的代价比较大,等待的线程空跑循环,自旋超过一定次数仍未获得锁再挂起;自适应自旋锁就是动态调整自旋次数的上界,如果上一次自旋成功了,则上调上界以期待这次自旋也成功,如果上一次失败了则降低对这次自旋成功的期望,下调上界;
(2)锁粗化,将多次在同一个锁上的加锁、解锁操作合并为一次;
(3)锁消除,无竞争则消除锁;
(4)轻量级锁
对象头分为两部分。第一部分用于存储对象自身的运行数据,如哈希码、分代年龄等,在32位和64位虚拟机中分别为32bit和64bit,称为Mark Word;第二部分存储指向方法区对象类型数据的指针。锁标志位不同时,Mark Word存储不同内容。
轻量级锁加锁时,在代码进入同步块时,如果同步对象锁状态为无锁态(锁标志位为01,是否偏向锁为0),虚拟机首先将在当前线程的栈帧中建立一个名为锁记录(Lock Record)的空间,用于存储锁对象目前的Mark Word的拷贝,官方称之为Displaced Mark Word,拷贝对象头中的Mark Word到Lock Record中,这时线程堆栈与对象头的状态如图所示:
然后,虚拟机使用CAS操作尝试将对象的Mark Word更新为指向Lock Record的指针。如果更新成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位变为‘00’,即表示此对象处于轻量级锁定状态,这时候线程堆栈与对象头的状态如图:
如果这个更新失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是说明当前线程已经拥有了这个对象的锁,继续执行同步块;否则说明这个锁对象已经被其它线程抢占了。如果有两条以上线程争用同一个锁,那轻量级锁就不再有效,要膨胀为重量级锁,锁标志的状态值变为‘10’,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。
有竞争时,轻量级锁比重量级锁更慢,因为多了额外的CAS操作。但是大部分锁在同步时没有竞争。
(5)偏向锁
当锁对象第一次被线程获取时,虚拟机设置锁标志位为‘01’,是否偏向锁为1,同时使用CAS操作把获取到这个锁的线程的ID记录在Mark Word中。如果CAS成功,持有偏向锁的线程每次获取这个锁时,只需要检查Mark Word和线程ID是否一致,避免了额外的CAS操作。当有另一个线程尝试获得锁(即Mark Word与线程ID不一致),偏向模式结束,恢复到未锁定或轻量级锁的状态。
2、ReentrantLock源码节选
public class ReentrantLock implements Lock, java.io.Serializable {
// sync继承AQS,ReentrantLock的加锁解锁实际调用的是sync的方法
private final Sync sync;
// sync的抽象类,用于实现公平锁和非公平锁
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
// 非公平地获得锁:如果没有线程持有锁,当前线程直接尝试CAS地获得锁,而不会等待自身成为等待队列的头结点
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// c == 0即没有线程持有锁,尝试CAS地获得锁
if (c == 0) {
if (compareAndSetState(0, acquires)) {
// 设置锁被当前线程持有
setExclusiveOwnerThread(current);
return true;
}
// 如果锁被占用,判断是不是当前线程占用的
} else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 如果锁被占用,且不是当前线程持有的,tryAcquire()失败,返回false
return false;
}
protected final boolean tryRelease(int releases) {
// tryRelease()时一定是当前线程持有锁(不是的话抛异常),无需额外的同步操作
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {
return getState() != 0;
}
}
// 非公平锁的实现
static final class NonfairSync extends Sync {
final void lock() {
// 第一次进入锁时,将锁设置为当前线程私有的
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
// 重新进入锁时,调用AQS的acquire(1),每次调用lock()时重入次数+1
acquire(1);
}
// 模板方法tryAcquire()调用了Sync里非公平获取锁的方法
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
// 公平锁的实现
static final class FairSync extends Sync {
// 没有检查持有锁的线程和当前线程是否一致,因为tryAcquire()里已经做了检查
final void lock() {
acquire(1);
}
// 重写AQS的tryAcquire(int acquires),
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
/**
* 与公平锁相比,多加了!hasQueuedPredecessors()判断当前线程节点是否为等待队列的头结点,
* 锁未被占用时,只有队列头结点的线程才能获得锁,因而是公平锁
*/
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
// 如果当前线程已经持有了锁,直接增加重入次数即可
} else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
// 默认是非公平锁的实现
public ReentrantLock() {
sync = new NonfairSync();
}
// 指定公平、非公平锁的实现
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
public void lock() {
sync.lock();
}
// 调用AQS的acquireInterruptibly(int args)方法,首先会检查中断位,被中断则抛出异常,再tryAcquire(int args)
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
// 尝试获得资源,直接返回true/false
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
// 尝试定时获取资源
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public void unlock() {
sync.release(1);
}
public Condition newCondition() {
return sync.newCondition();
}
}
以上节选了1.8的ReentrantLock的主要源码,已经写得很明白了,再补充一些
tryLock()是可轮询、可定时的锁,如果锁未被占用但是CAS(0, 1)失败,或者锁被其他线程占用,则返回false,可以避免死锁。如下代码如果有另一条线程先调用lock2.tryLock(),再lock1.tryLock()也不会死锁,但是可能会活锁,可以让两个线程等待一个随机事件再进行lock1和lock2上的tryLock()。
while(true){
if(lock1.tryLock()){
if(lock2.tryLock()){
doSomething();
}
}
}
lockInterruptibly()提供了可中断的锁获取操作,利用了AQS的acquireInterruptibly(int args),首先检查线程的中断位,如果线程被中断则抛出InterruptedException,未中断再tryAcquire()。
公平锁和非公平锁:性能上非公平锁>公平锁,因为非公平锁减少了线程挂起和唤醒的开销。例如线程A释放锁,公平锁需要唤醒头结点B,此时有线程C需要获取锁,如果是非公平锁,C可能在B醒来之前就已经获得并释放锁了,但是对于公平锁,C必须等待头结点B获取并释放锁。
非公平锁:线程需要获取锁时,不是首先进队,而是先尝试获取资源(如果锁未被占用则CAS改变state,成功的话直接占有了锁;如果锁被占用,检查是不是自身占有的锁),获取失败再入队等待。
公平锁:资源被释放时,唤醒队列头结点,只有队列的头结点才能获取资源。
3、synchronized和ReentrantLock的区别
(1)synchronized是jvm层面的,做了自旋锁、锁粗化、锁消除、轻量级锁和偏向锁等优化,ReentrantLock是api层面的。1.6优化了以后两者的性能差不多,没有特殊需求用synchronized即可。
(2)ReentrantLock提供了一些synchronized实现不了的方法。例如tryLock()可以不阻塞地尝试获得锁,定时尝试获得锁;lockinterruptibly()可中断的锁获取操作;还提供了公平锁、非公平锁、Condition等实现。而synchronized会阻塞,不能定时获得锁,不可中断,使用的是非公平锁。