锁的作用在于将原来的并行改为串行,除了synchronized可以加锁,还可以用ReentrantLock加锁保证线程安全。
思路:标记,通过这个标记来判断是否有线程在执行,如果没有,线程能够执行并且把标记改为在执行,改这个标记必须满足保证原子性、可见性,保证安全性。其它线程如果判断这个标记有线程在执行,那么我要么排队(lock),要么返回没有拿到锁(tryLock)。
模拟源码:
一个方法加锁(lock),执行完成后,释放锁(unlock);
1. ReentrantLock加锁:
ReentrantLock对象通过lock()方法进行加锁,这锁是非公平锁NonFairSync类中的lock()方法。
compareAndSetState(0, 1)表示通过cas去更改aqs里面的state字段 保证安全性,将0变成1。这保证了原子性。并且state字段用了volatile修饰,保证了判断条件compareAndSetState(0, 1)的可见性。
final void lock() {
//1和0保证原子性,volatile修饰保证可见性
if (compareAndSetState(0, 1))
//拿到锁的线程进入
setExclusiveOwnerThread(Thread.currentThread());
else
//拿不到锁的线程进入
acquire(1);
}
protected final void setExclusiveOwnerThread(Thread thread) {
//标记哪个线程拿到了锁
exclusiveOwnerThread = thread;
}
假如thread1拿到锁后就执行了业务代码
thread2没有拿到锁,compareAndSetState(0, 1)为false,会走acquire(1)方法。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire(arg)方法时尝试去拿锁,tryAcquire(arg)是通过NonFairSync类的tryAcquire(arg)方法里的nonfairTryAcquire方法实现。
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取state
int c = getState();
//state为0时说明了,上一个线程执行完成,释放了锁,当前线程获取锁开始执行任务
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//重入锁,就是同一个线程可以加锁多次,目的是减少死锁,一般锁都是可重复的
//判断当前线程与占有锁的线程是不是同一个线程
else if (current == getExclusiveOwnerThread()) {
//重入次数+1
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//赋值重入次数,重入数,就是加了几次锁就得释放几次锁
setState(nextc);
return true;
}
return false;
}
Node.EXCLUSIVE = null,并且Node是一个数据结构,维护在AQS下面。在addWaiter方法中,假设没有拿到锁的线程thread2,通过enq()方法中,addWaiter最终会返回一个包含thread2的Node。
//Node.EXCLUSIVE = null;
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
//node包含thread2,arg=1
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取node的前一个节点
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);
}
}
最终会走到parkAndCheckInterrupt(),在parkAndCheckInterrupt方法里面会走到LocakSupport.park(this);阻塞,等待别人来唤醒你。waitStatus为-1是可唤醒状态
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
2. 释放锁流程
随着thread1业务代码执行完了,我要去unlock释放锁。
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) { //第一件事情是先去将标记state改为0
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;//代表重入次数减1,拿了几次就释放几次
if (Thread.currentThread() != getExclusiveOwnerThread()) //当前线程是不是拿到锁的线程
throw new IllegalMonitorStateException();
boolean free = false; //代表是不是真的释放锁,还是重入次数减1
if (c == 0) { //c==0 表示锁已经完全释放了
free = true;
setExclusiveOwnerThread(null); //把加锁的线程赋值给null,代表线程没有锁了
}
setState(c);//重新赋值state,如果state为0代表其它线程可以抢到锁了,新进来的线程和需要唤醒的线程可以去拿到锁了
return free; //free的值,如果完全释放了,就是true,如果不是则是false;
}
private void unparkSuccessor(Node node) { //node为head节点
int ws = node.waitStatus;//-1
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);//改为0
Node s = node.next;//s是thread2的node
if (s == null || s.waitStatus > 0) { //代表数据异常或者节点异常
s = null;
for (Node t = tail; t != null && t != node; t = t.prev) //node节点从后往前遍历
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);//唤醒了thread2
}
到此,线程thread1执行完成。主要线程入了队列,那么就要一个一个唤醒。
假设又有一个线程来执行lock,因为state=0(thread释放了),所以thread2就算unpark了,但是拿不到锁,继续park。
thread4明明比thread2慢来的,但是拿到了锁,叫做非公平锁,针对的是没有入队列的线程,如果进入了队列,那么就是公平的。
公平锁,就算thread4是后来的,但也必须去排队,如果不在队列里就拿不到锁。