ReentrantLock和Synchronized比较
优点:
1.ReentrantLock可以指定是公平锁还是非公平锁,默认是非公平锁。
而Synchronized只能是非公平锁,可能存在线程饥饿。
ReentrantLock构造器源码:
/**
* 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();
}
2.ReentrantLock可以用Condition分组唤醒需要唤醒的线程
而Synchronized要么用notify随机唤醒一个线程,要么用notifyAll唤醒所有线程。
代码示例:生产者消费者
对于一个锁,可以为多个线程间建立不同的Condition
await()和signal()之前必须要先lock()获得锁
await()会释放锁,然后挂起线程,等待被唤醒。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @program: concurrency
* @description:
* @author: Sid
* @date: 2019-01-10 11:22
* @since: 1.0
**/
public class ConditionProductConsumer {
final Object[] items = new Object[100];
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
private int head, tail,count;
public void put(Object t) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
notFull.await();
}
items[tail] = t;
if (++tail == items.length) {
tail = 0;
}
++count;
notEmpty.signalAll();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await();
}
Object ret = items[head];
items[head] = null;
if (++head == items.length) {
head = 0;
}
--count;
notFull.signalAll();
return ret;
} finally {
lock.unlock();
}
}
}
3.ReentrantLock可以中断等待锁的线程,lock.lockInterruptibly()
而Synchronized不能。
1.无条件地轮询获取锁lock.lock()
线程尝试获取锁操作失败后,在等待过程中,如果该线程被其他线程中断了,lock方法不会立即中断,只是记录中断状态,继续参与锁的竞争直到成功,再根据中断标识处理中断。如果出现死锁,那么lock方法就无法被终止。
如果资源被其他线程锁了,会阻塞!
AQS源码
lock获取锁过程中,在一个死循环里面一直获取锁和检测中断,但是检测到中断并不会真的立刻中断,而只是标记中断,在成功获取锁之后,再根据中断标识处理中断,即selfInterrupt中断自己。
public final void acquire(int arg) {
//直接获取锁成功则完成acquire方法
//直接获取锁失败,会进入一个队列,一直死循环去获取锁
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//在队列中一直死循环获取到锁以后,出来才发现线程被中断,这时才真的调用中断线程的方法
selfInterrupt();
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
//死循环
for (;;) {
final Node p = node.predecessor();
//1.一直尝试获取锁,只有真的获取到锁才会结束死循环
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//2.如果获取锁失败会走到这里来,park线程挂起
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//如果检测到中断,仅仅只是做一个flag标记,并不会真的中断线程
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
2.可中断锁lock.lockInterruptibly()
线程尝试获取锁操作失败后,在等待过程中,如果该线程被其他线程中断了,lockInterruptibly则直接抛出中断异常来立即响应中断,由上层调用者处理中断。
如果要求被中断线程不能参与锁的竞争操作,则此时应该使用lockInterruptibly方法,一旦检测到中断请求,立即返回不再参与锁的竞争并且取消锁获取操作
如果资源被其他线程锁了,会阻塞!
AQS源码:
public final void acquireInterruptibly(int arg)
throws InterruptedException {
//一旦检测到中断请求,就立马抛出InterruptedException异常
if (Thread.interrupted())
throw new InterruptedException();
//如果尝试获取锁失败
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
//1.在一个死循环中一直获取锁,直到获取到锁,才结束死循环
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
//如果获取锁失败会走到这里来,park线程挂起
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//如果检测到中断,则立马抛出InterruptedException异常
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
4.非阻塞轮询
tryLock()和lockInterruptibly()类似,在参与竞争锁的过程中可以立即被中断,抛出InterruptedException异常。
tryLock()方法仅在调用时锁为空闲状态才获取该锁。如果锁可用,则获取锁,并立即返回值true。如果锁不可用,则此方法将立即返回值false。不会阻塞。
tryLock(long timeout, TimeUnit unit) 是一个具有超时参数的尝试申请锁的方法,阻塞时间不会超过给定的值;如果成功则返回true,超时则返回false。
缺点:
1.使用Synchronized,JVM会为我们释放锁
而ReentrantLock必须自己手动释放锁,通常是把lock.unlock写在finally中。如果没执行到,就呵呵了。
private final static Lock lock = new ReentrantLock();
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
2.JVM用Synchronized管理锁的请求和释放,JVM在生成线程转储时能够包括锁的信息,能标识死锁和其他异常行为的来源
而Lock是JDK实现的,只是一个普通的Java类,JVM是不知道具体哪个线程拥有Lock对象。