谈谈java可重入锁
借鉴:https://mp.weixin.qq.com/s/GDno-X1N8zc98h9MZ8_KoA
大多数java开发都会遇到多线程开发,多线程开发过程中往往会遇到并发问题,而解决并发问题就是对资源加锁,希望程序执行是某一时刻只有一个线程访问该共享资源。
可重入锁顾名思义:可以重复加锁即某一线程可以对此资源重复加锁。
java中的锁都是实现lock接口的,他是锁的一个顶级接口
重点来了ReentrantLock就是可重入锁,实现了lock接口。
ReentrantLock里面主要的方法
- void lock():加锁,如果锁已经被别人占用了,就***无限等待***。
- boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException:尝试获取锁,等待timeout时间。同时,可以响应中断。这是一个比单纯lock()更具有工程价值的方法,tryLock()在JDK内部被大量的使用。
与lock()相比,tryLock()至少有下面一个好处:可以不用进行无限等待。直接打破形成死锁的条件。如果一段时间等不到锁,可以直接放弃,同时释放自己已经得到的资源。这样,就可以在很大程度上,避免死锁的产生。因为线程之间出现了一种谦让机制可以在应用程序这层进行进行自旋,你可以自己决定尝试几次,或者是放弃。等待锁的过程中可以响应中断,如果此时,程序正好收到关机信号,中断就会触发,进入中断异常后,线程就可以做一些清理工作,从而防止在终止程序时出现数据写坏,数据丢失等悲催的情况。当然了,当锁使用完后,千万不要忘记把它释放了。不然,程序可能就会崩溃啦~ - void unlock() :释放锁 此外, 重入锁还有一个不带任何参数的tryLock()。
- public boolean tryLock() 这个不带任何参数的tryLock()不会进行任何等待,如果能够获得锁,直接返回true,如果获取失败,就返回false,特别适合在应用层自己对锁进行管理,在应用层进行自旋等待。
实现原理
实现重入锁的方法很简单,就是基于一个状态变量state。这个变量保存在AbstractQueuedSynchronizer对象中。
当state为0时表示在空闲状态,线程可以对它加锁,下面是加锁的实现:
final void lock() {
// compareAndSetState就是对state进行CAS操作,如果修改成功就占用锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//如果修改不成功,说明别的线程已经使用了这个锁,那么就可能需要等待
acquire(1);
}
acquire(1)实现:
public final void acquire(int arg) {
//tryAcquire() 再次尝试获取锁,
//如果发现锁就是当前线程占用的,则更新state,表示重复占用的次数,
//同时宣布获得所成功,这正是重入的关键所在
if (!tryAcquire(arg) &&
// 如果获取失败,那么就在这里入队等待
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//如果在等待过程中 被中断了,那么重新把中断标志位设置上
selfInterrupt();
}
公平重入锁
公平锁即如果有1,2,3,4 这四个线程,按顺序,依次请求锁。那等锁可用的时候,谁会先拿到锁呢?而java默认情况下是非公平锁,即随机加锁的
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
FairSync公平锁 NonfairSync非公平锁
那公平锁和非公平锁实现的核心区别在哪里呢?来看一下这段lock()的代码:
//非公平锁
final void lock() {
//上来不管三七二十一,直接抢了再说
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//抢不到,就进队列慢慢等着
acquire(1);
}
//公平锁
final void lock() {
//直接进队列等着
acquire(1);
}
由源码可得:公平锁进来一个线程,直接进队列,顺序执行的,非公平锁,当有线程要加锁,直接先加锁,抢到了就加锁成功,抢不到就进队列等待吧。
tryLock也是相似的操作
//非公平锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//上来不管三七二十一,直接抢了再说
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果就是当前线程占用了锁,那么就更新一下state,表示重复占用锁的次数
//这是“重入”的关键所在
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;
}
//公平锁
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;
}
释放锁就是state–
完结撒花!
写在最后,CAS即UnSafe.compareAndSetState是java的本地方法,都说UnSafe是魔法类,其实从名字上就能得出来,他是不安全的类,因为这个类是可以直接操作内存的,如果使用不当,很可能会内存泄漏,内存溢出,所有这个类被final修饰,不能被继承,目的就是让开发者少用这个类的方法,它本身里面的方法都是调用c/c++的方法。具体c/c++怎么实现的,hxd们可以自己去瞅瞅哦!
最最最后引用三太子的话:你知道的越多,你不知道的越多,我们下期再见!