Synchronized vs ReentrantLock
ReentrantLock概述
ReentrantLock是可重入的独占锁,同时只能有一个线程可以获取该锁,其他获取该锁的线程会被阻塞而被放入该锁的AQS阻塞队列里面。
类图结构如下
底层基于AQS实现,ReentrantLock的lock等方法,委托给其依赖sync的lock方法
AQS 是典型的模板方法设计模式,父类(AQS)定义好骨架和内部操作细节,具体规则由子类去实现
从类图可以看出,ReentrantLock最终还是使用AQS来实现的,并且根据参数来决定其内部是一个公平还是非公平锁,默认是非公平锁。
/** * Creates an instance of {@code ReentrantLock}. * This is equivalent to using {@code ReentrantLock(false)}. */ public ReentrantLock() { sync = new NonfairSync(); } /** * Creates an instance of {@code ReentrantLock} with the * given fairness policy. * * @param fair {@code true} if this lock should use a fair ordering policy */ public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
其中Sync类直接继承自AQS,它的子类NonfairSync和FairSync分别实现了获取锁的非公平与公平策略。
在这里,AQS的state状态值表示线程获取该锁的可重入次数。
在默认情况下,state的值为0表示当前锁没有被任何线程持有。
当一个线程第一次获取该锁时会尝试使用CAS设置state的值为1,如果CAS成功则当前线程获取了该锁,然后记录该锁的持有者为当前线程。
在该线程没有释放锁的情况下第二次获取该锁后,状态值被设置为2,这就是可重入次数。
在该线程释放该锁时,会尝试使用CAS让状态值减1,如果减1后状态值为0,则当前线程释放该锁。
获取锁
void lock()
.
当一个线程调用该方法时,说明该线程希望获取该锁。
如果锁当前没有被其他线程占用并且当前线程之前没有获取过该锁,则当前线程会获取到该锁,然后设置当前锁的拥有者为当前线程,并设置AQS的状态值为1,然后直接返回。
如果当前线程之前已经获取过该锁,则这次只是简单地把AQS的状态值加1后返回。
如果该锁已经被其他线程持有,则调用该方法的线程会被放入AQS队列后阻塞挂起。
加锁过程如下
/** * Acquires the lock. * * <p>Acquires the lock if it is not held by another thread and returns * immediately, setting the lock hold count to one. * * <p>If the current thread already holds the lock then the hold * count is incremented by one and the method returns immediately. * * <p>If the lock is held by another thread then the * current thread becomes disabled for thread scheduling * purposes and lies dormant until the lock has been acquired, * at which time the lock hold count is set to one. */ public void lock() { sync.lock(); }
在如上代码中 ,ReentrantLock的lock()委托给了sync类,根据创建ReentrantLock构造函数选择sync的实现是NonfairSync还是FairSync,这个锁是一个非公平锁或者公平锁。
非公平锁的实现代码
先看sync的子类NonfairSync的情况,也就是非公平锁时。
/** * Sync object for non-fair locks */ 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() { // 1 cas 设置状态值 if (compareAndSetState(0, 1)) // 设置线程为独占锁线程 setExclusiveOwnerThread(Thread.currentThread()); else // 2 调用AQS的acquire方法 acquire(1); } protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }
在代码(1)中,因为默认AQS的状态值为0,所以第一个调用Lock的线程会通过CAS设置状态值为1,CAS成功则表示当前线程获取到了锁,然后setExclusiveOwnerThread设置该锁持有者是当前线程.
如果这时候有其他线程调用lock方法企图获取该锁,CAS会失败,然后会调用AQS的acquire方法。注意,传递参数为1,这里再贴下AQS的acquire的核心代码。
/** * Acquires in exclusive mode, ignoring interrupts. Implemented * by invoking at least once {@link #tryAcquire}, * returning on success. Otherwise the thread is queued, possibly * repeatedly blocking and unblocking, invoking {@link * #tryAcquire} until success. This method can be used * to implement method {@link Lock#lock}. * * @param arg the acquire argument. This value is conveyed to * {@link #tryAcquire} but is otherwise uninterpreted and * can represent anything you like. */ public final void acquire(int arg) { // 3 调用ReentrantLock重写的tryAcquire方法 if (!tryAcquire(arg) && // tryAcquire返回false会把当前线程放入AQS阻塞队列 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
AQS并没有提供可用的tryAcquire方法,tryAcquire方法需要子类自己定制化,所以这里代码(3)会调用ReentrantLock重写的tryAcquire方法。我们先看下非公平锁的代码。
protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }
/** * Performs non-fair tryLock. tryAcquire is implemented in * subclasses, but both need nonfair try for trylock method. */ final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 4 当前AQS状态值state为0 if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) {// 5 当前线程是该锁的持有者 int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
代码(4)会查看当前锁的状态值是否为0,为0则说明当前该锁空闲,那么就尝试CAS获取该锁,将AQS的状态值从0设置为1,并设置当前锁的持有者为当前线程然后返回,true。
如果当前状态值不为0则说明该锁已经被某个线程持有,所以代码(5)查看当前线程是否是该锁的持有者,如果当前线程是该锁的持有者,则状态值加1,然后返回true,这里需要注意,nextc<0说明可重入次数溢出了。
如果当前线程不是锁的持有者则返回false,然后其会被放入AQS阻塞队列。
非公平锁是如何体现的?
上面看完了非公平锁的实现代码,回过头来看看非公平在这里是怎么体现的。
首先非公平是说先尝试获取锁的线程并不一定比后尝试获取锁的线程优先获取锁。
这里假设线程A调用lock()方法时执行到nonfairTryAcquire的代码(4),发现当前状态值不为0,所以执行代码(5),发现当前线程不是线程持有者,则执行代码(6)返回false,然后当前线程被放入AQS阻塞队列。
这时候线程B也调用了lock()方法执行到nonfairTryAcquire的代码(4),发现当前状态值为0了(假设占有该锁的其他线程释放了该锁),所以通过CAS设置获取到了该锁。明明是线程A先请求获取该锁呀,这就是非公平的体现。
这里线程B在获取锁前并没有查看当前AQS队列里面是否有比自己更早请求该锁的线程,而是使用了抢夺策略。