我们平时在项目中遇到线程安全问题,大多使用同步来解决同步问题,同步是隐式锁,它的加锁与释放,无需我们关注,虚拟机会处理。而ReetrantLock的加锁与释放都需要我们手动处理。下面我们将从源码角度来分析ReetrantLock原理。若有不足,欢迎大家留言,以便及时改正。
一:ReetrantLock实现了锁接口,以及三个内部类,Sync,FairSync(公平锁),NonfairSync(非公平锁)。
1:Sync是继承AbstractQueuedSynchronizer(以下简称AQS)的抽象类,AQS是基于CAS并发的核心类。
2:FairSync,NonfairSync是Sync的实现类.ReetrantLock默认是NonfairSync非公平锁
3:ReetrantLock的使用方式。
Lock lock = new ReentrantLock();
lock.lock();
try {
//加锁区域
} finally {
lock.unlock(); //必须要解决,否则不释放锁的占用
}
4:ReetrantLock也支持重入锁,加锁的个数要在最后与解锁个数相同。
5:ReetrantLock部分方法
public class ReentrantLock {
private final Sync sync;
public ReentrantLock() {
sync = new NonfairSync(); //默认非公平锁
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
//加锁
public void lock() {
sync.lock();
}
//释放锁
public void unlock() {
sync.release(1);
}
//可中断锁
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
}
从以上方法可以看出ReetrantLock锁可中断,而synchronized同步不可中断
二:我们需要首先介绍下AbstractQueuedSynchronizer(AQS).AQS是基于CAS并发通过节点建立双向链表,存放等待线程队列。
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
//用于存储头节点,仅作为指向,不存储等待线程信息。
private transient volatile Node head;
//队列尾部,每次增加等待队列线程就是改变tail的指向,并重新赋值新等待线程节点。
private transient volatile Node tail;
//当有线程占用锁,state大于0,其他线程处于等待状态,无法获取锁。
private volatile int state;
Node nextWaiter;
// CAS的全称是Compare And Swap 即比较交换,执行的是CPU指令集,通过硬件层次保证原子性
// CAS操作的执行依赖于Unsafe类中的方法
private static final Unsafe unsafe = Unsafe.getUnsafe();
private final boolean compareAndSetHead(Node update) {
//public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
//headOffset表示head属性在该对象中的偏移量,null表示head预期值,表示将head替换成update新值
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
private final boolean compareAndSetTail(Node expect, Node update) {
//tailOffset表示tail属性在该对象中的偏移量,expect表示tail预期值,表示将tail替换成update新值
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
}
以下我们通过lock.lock();与lock.unlock(); 的源码来分析其原理。首先来讲非公平锁NonfairSync
三:NonfairSync(非公平锁).NonfairSync不根据抢占锁的顺序来获得锁。
static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1)) //更改AQS中的状态state,默认是0,表示该锁未被占用状态,如果更改成功则当前线程获取锁
setExclusiveOwnerThread(Thread.currentThread()); //设置当前线程,当前线程获取该锁
else
acquire(1); //如果获取锁失败,则重新获取
}
//
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
以下是compareAndSetState源码
//更改AQS中的状态state,默认是0,如果更改成功则当前线程获取锁
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
//setExclusiveOwnerThread源码,将当前线程设置为锁的拥有者
protected final void setExclusiveOwnerThread(Thread t) { //将当前线程设置为锁的拥有者
exclusiveOwnerThread = t;
}
//获取锁失败后重新获取
public final void acquire(int arg) {
//tryAcquire方法是非公平锁 NonfairSync的方法,最终调用以下父类的nonfairTryAcquire方法,如果获取锁成功返回true,
//则不执行acquireQueued
if (!tryAcquire(arg) &&
//以下分别对addWaiter于acquireQueued方法进行分析,Node.EXCLUSIVE表示该锁对应私有的,非共享
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//重新获取锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) { //如果没有线程获取到该锁,则重新获取锁
if (compareAndSetState(0, acquires)) { //如果获取锁成功,则将当前线程设置为该锁的拥有者,并返回 true
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
//此时已经有线程获取到该锁,则判断是否是当前获取到的锁,如果是则将状态加1,然后设置状态,并返回true
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false; //该线程位获取到锁
}
//添加等待节点,将该节点添加到队列尾部
private Node addWaiter(Node mode) {
Node node = new Node(mode);//创建一个绑定线程的节点
for (;;) {
Node oldTail = tail;
if (oldTail != null) {
//设定给定偏移量的值,将node的上一个节点指向 oldTail
U.putObject(node, Node.PREV, oldTail);
if (compareAndSetTail(oldTail, node)) { //替换原有 oldTail 值为新的节点 node
oldTail.next = node; //将oldTail下一个节点指向 新添加的 node
return node; //返回新节点
}
} else {
initializeSyncQueue(); //如果最后一个节点不存在,则初始化链表 ,以下对initializeSyncQueue分析
}
}
}
//当第一次创建等待链表
private final void initializeSyncQueue() {
Node h;
if (U.compareAndSwapObject(this, HEAD, null, (h = new Node()))) //创建一个新节点,不绑定任何线程信息
tail = h; //将头节点赋值给 最后节点 tail
}
以下是将未获取到锁的线程放入等待链表里,并挂起线程
final boolean acquireQueued(final Node node, int arg) {
try {
boolean interrupted = false;
for (;;) { //自旋
final Node p = node.predecessor(); //获取该节点的前一个节点
if (p == head && tryAcquire(arg)) { //如果前节点是头节点并获取到锁,则设置头结点
setHead(node);
p.next = null; // help GC
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && //判断是否需要挂起线程
parkAndCheckInterrupt()) //挂起线程,调用了LockSupport.park(this);
interrupted = true;
}
} catch (Throwable t) {
cancelAcquire(node);
throw t;
}
}
以上是对我们调用ReentrantLock中锁方法源码的分析。当我们调用锁方法时,内部会判断是否有线程获取锁,如果没有则继续向下执行acquire(int arg)方法,如果有其他线程占用锁,则重新获取锁tryAcquire(ARG),如果没有线程获取锁,则继续向下执行acquireQueued(addWaiter(Node.EXCLUSIVE),ARG),如果有线程获取到,则判断是否是当前线程,如果是,则状态加1继续执行,否则需要将该线程放入等待链表中然后通过自旋死循环来判断当前是否获取到锁,获取到则继续执行锁中的代码以下是逻辑图:
四:公平锁FairSync
公平锁则是按照线程请求锁的顺序来获取锁,与NonfairSync区别在于公平锁在获取锁时,如果没有线程占用锁并且没有等待线程链表则获取锁,如果有线程获取锁或者有等待线程存在则将该线程添加到链表尾部。
五:ReentrantLock中newCondition方法介绍
newCondition方法返回了AQS中ConditionObject对象,ConditionObject实现了Condition接口
public interface Condition {
//让线程处于等待状态
void await() throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
//等待并设置等待时间
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
//唤醒线程
void signal();
void signalAll();
}
一下是简单的生产者消费者,采用ReentrantLock不同的Condition等待或唤醒
public class Product {
ReentrantLock lock = new ReentrantLock();
Condition producer = lock.newCondition();
Condition consumer = lock.newCondition();
int product = 10;
//生产
public void producer() {
lock.lock();
try {
//
if (product >0) {
System.out.println("##仓库已满,停止生产");
producer.await();
}
product++;
System.out.println("##开始生产");
Thread.sleep(1000);
System.out.println("##生产完毕,仓库剩余个数= " + product);
consumer.signal(); //唤醒消费
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//消费
public void consumer() {
lock.lock();
try {
//
if (product < 5) {
System.out.println("**仓库已空,请求生产商品");
producer.signal(); //唤起生产
consumer.await(); //停止消费
}
System.out.println("**开始消费");
product--;
Thread.sleep(1000);
System.out.println("**消费完毕,仓库剩余个数= " + product);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
执行代码
final Product product = new Product();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
product.producer();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
product.consumer();
}
}).start();
}
运行结果
以上可以看出:不同的条件等待和唤醒,不影响其他条件,这样可以减少生产者与消费者同时抢占资源,提高性能。
总结:
1:ReetrantLock锁实际上是通过AQS来实现的,AQS则通过不安全的这个类实现CAS操作,实现线程安全。
2:ReetrantLock相比同步更加灵活,可中断,可以获取等待该锁的线程个数,以及获取锁对应的线程。