提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
作用
线程A想要独占式的访问一个同步代码块,可以在同步代码块的前后 使用 lock() 、unlock()进行加锁。达到互斥访问的目的。
使用
public class Main{
public static void main(String[] args) {
Lock lock = new ReentrantLock();
new Thread(new Runnable() {
@Override
public void run() {
try {
lock.lock();
System.out.println("t1 开始执行");
Thread.sleep(5000);
ok();
System.out.println("t1 结束执行");
lock.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t1").start();
new Thread(new Runnable() {
@Override
public void run() {
try {
lock.lock();
System.out.println("t2 开始执行");
Thread.sleep(5000);
ok();
System.out.println("t2 结束执行");
lock.unlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"t2").start();
}
public static void ok(){
System.out.println("ok");
}
}
t1 开始执行
ok
t1 结束执行
t2 开始执行
ok
t2 结束执行
设计思路
基于aqs
(0)独占模式,进入同步代码块的线程只能有一个
(1)使用state表示当前线程的可重入次数
(2)重写了AQS中tryAcquireShared()、tryReleaseShared(),根据ReentrantLock自身特性重新定义了竞争锁、释放锁的策略
自身特性如下:
state>0,说明已经有线程加锁。
此时判断下,持有锁的线程是否就是自己,如果是表示可重入锁。否则,排队。
(4)所有等待节点以独占模式阻塞,每次唤醒,只会唤醒当前结点的下一个。
源码分析
ReentrantLock初始化
默认为非公平锁,可以指定
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
根据自身特性重写方法
非公平锁与公平锁实现一样,不过非公平锁,最开始会CAS尝试竞争锁
state>0 说明有线程加锁,此时判断该线程是否是自己,如果是,表示可重入
state=0 说明无线程加锁,判断队列中是否有线程在排队
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;
}
}
//当前线程判断是否需要排队
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
//【h != t的逻辑】
//(1)当队列头尾结点相等时 都为null 或者 都是vNode 直接返回false
//(2)当队列中有除了vNode之外的其他结点,return true,此时继续向后执行
/**
* 这里注意 h.next实际上是队列中的第二个结点,h是vNode
* 因为源码认为 队列中的第一个结点实际上是持有CPU的结点,所以会创建一个虚拟节点作为,这个虚拟节点不涉及排队,其内部的线程=null
* 【(s = h.next) == null】如果=null,说明此时已经有其他线程刚刚获取锁,所以当前线程作为首个排队线程,直接排队
* 【s.thread != Thread.currentThread()】 如果s.thread = Thread.currentThread(),说明当前线程就是首个排队线程,此时尝试获取锁
*/
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
cas操作将state-1
state=0 完全释放锁
state>0 可重入情况,未完全释放
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
线程调用lock()加锁
非公平锁,竞争锁之前,CAS尝试一次
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
公平锁
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
线程调用unlock解锁
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}