首先是两者区别:
synchronized属于非公平锁,不支持锁共享,功能单一。
ReentrantLock功能比较丰富:公平锁、尝试加锁,带超时时间的获取锁,获取锁支持响应中断等场景都适合使用。
详细解释ReentrantLock功能
公平锁&不公平锁:
其实源码中解释的很简单粗暴:
/**
* 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();
}
(emmmmmmm.jpg)
实现机制也是大致相同,
公平锁,加锁前会先看下前面有没有其他线程在排队,如果前面有其他线程排队,就乖乖排在队尾。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
}
非公平锁的流程是他不管前面有没有线程在排队,都先尝试获取一下锁,获取成功就直接占用。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
关键在于hasQueuedPredecessors这方法,用于查看当前队列是否有其他排队
尝试加锁
synchronized就像专一的男人,一旦发起加锁流程,就一心等待,即使这时女孩有男朋友(其他线程抢占),他还是会一直等待。
ReentrantLock不一样,他有个技能,叫 tryLock,尝试获取女孩子的心,发现一旦有其他竞争对象,他立马折返撤退。而且这个tryLock 还有个特殊技能,获取锁支持带超时时间,如果女孩在指定时间内没给机会,就会返回。(海王本王石锤了)
代码中trylock调用的是sync中的nonfairTryAcquire方法,而不是在nonfairSync中(就像代表正义的反派角色,或者反过来 )
/**
* 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();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
带超时时间获取锁
先设置超时时间,单位nano(System.nanoTime)然后调用tryAcquireNanos——doAcquireNanos方法
(注释太长)
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
###########################################################
(注释太长)
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
###########################################################
/**
* Acquires in exclusive timed mode.
*
* @param arg the acquire argument
* @param nanosTimeout max wait time
* @return {@code true} if acquired
*/
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
在方法中可以看到
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
与获取锁流程类似,只是加了一个超时时间的判断,先用当前时间加超时间隔时间得到一个到期时间,然后在获取锁的for 循环体中判断是否超时。
大家注意到调用shouldParkAfterFailedAcquire 方法判断是否需要park的时候,如果返回true 会再判断nanosTimeout > spinForTimeoutThreshold。
spinForTimeoutThreshold 这个参数是什么意思?
这个值是自旋的最小阈值,这里被Doug Lea 设置成了1000,表示1000纳秒,也就是说如果剩余的时间不足1000纳秒,则不需要park。
static final long spinForTimeoutThreshold = 1000L;
为什么不需要park,因为离到期时间太短,阻塞之后再唤醒的时间可能都不够(这里是<1000纳秒),代价还很高。
所以最好的方式是下一次for 循环要么获取到锁,要么滚蛋,不浪费一次pack 再 unpack的代价。
响应中断
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
这里是如果应该park,并且发生了中断,设置中断标识信息(interrupted字段),获取到锁之后返回这个中断标识信息。
/**
* Convenience method to park and then check if interrupted
*
* @return {@code true} if interrupted
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
parkAndCheckInterrupt这个方法会让线程park(阻塞),同时会调用 Thread.interrupted() 返回线程是否发生过中断,这个方法会清除线程的中断标识位,是否发生过中断的状态保持在 interrupted 里面,后面返回了。
再回来
只要发生了中断,直接抛 InterruptedException 异常,获取锁流程不再继续。
-
所以说ReentrantLock 如果使用 lockInterruptibly,如果发生了中断是会立即停止获取锁的流程,是响应中断的,
-
但是如果使用 lock 是不响应中断,实际上是把中断吃掉了,延迟中断(中断标识返回后主动中断)。
得知:synchronized 获取锁的过程中是不能被中断,ReentrantLock 支持。
总结
底层实现:synchronized 是 JVM层面的锁,是Java关键字,通过monitor对象来完成(monitorenter与monitorexit),ReentrantLock 是从jdk1.5以来(java.util.concurrent.locks.Lock)提供的API层面的锁。
锁的对象:synchronzied 锁的是对象,锁是保存在对象头里面的,根据对象头数据来标识是否有线程获得锁/争抢锁;ReentrantLock 是根据volatile 变量 state 标识锁的获得/争抢。
实现机制上:synchronized 的实现涉及到锁的升级,具体为无锁、偏向锁、自旋锁、向内核态申请重量级锁,ReentrantLock实现则是通过利用CAS(CompareAndSwap)自旋机制保证线程操作的原子性和volatile 保证数据可见性以实现锁的功能。
释放锁方式上:synchronized 不需要用户去手动释放锁,synchronized 代码执行完后系统会自动释放锁;ReentrantLock 需要用户去手动释放锁,如果没有手动释放锁,就可能导致死锁现象。一般通过lock() 和unlock() 方法配合 try/finally 语句块来完成。
带条件的锁:synchronized不能绑定条件;ReentrantLock 可以绑定Condition 结合await()/singal() 方法实现线程的精准唤醒,而不是像synchronized通过Object类的wait()/notify()/notifyAll() 方法要么随机唤醒一个线程要么唤醒全部线程。