JUC-locks锁
如有侵权,请联系~
如有错误,也欢迎批评指正~
1、JUC-locks锁概述
java的并发包【JUC】下面就两个子包,一个是atomic原子包,另一个就是lock锁包。这个包下就提供了三种锁:ReentrantLock【可重入锁】、ReentrantReadWriteLock【可重入读写锁】、StampedLock【更轻量级的读写锁】。
这些锁的实现大部分都是基于AbstractQueuedSynchronizer【俗称的AQS,分两篇文章,防止难以消化】类实现的。
2、管程模型
管程模型是并发编程的万能钥匙,juc包下的锁以及阻塞队列几乎都是使用的管程模型实现的。当然管程模型【java选择】和信号量是【操作系统】等价的,这两个都是用来解决并发问题,以使用信号量实现管程,也可以使用管程实现信号量。
管程就是指管理共享变量,以及对共享变量的相关操作,对应于java中类的属性和方法。管程主要是三种模型:Hasen 模型、Hoare 模型和 MESA 模型,Java 使用的是 MESA 模型。
管程特点:
- 封装性:管程将共享资源和操作这些资源的同步机制进行封装,共享变量只能被管程中的方法访问,外部过程不能访问。
- 互斥访问:管程确保每次只有一个线程能够执行管程中的代码。当一个线程在管程中执行时,其他试图进入该管程的线程被阻塞,直到当前线程退出管程。
- 条件变量:管程通常可以有一个或多个条件变量,用于线程间的条件同步。线程可以在管程内部等待某个条件成立(通过调用某个等待函数),同时释放锁,直到条件满足时得到通知。
管程模型的整体架构图:
3、ReentrantLock可重入锁
ReentrantLock可重入锁支持公平锁也支持非公平锁(具体实现可以查看各自的tryAcquire()方法),选择公平锁还是非公平锁根据创建该对象时候的参数。
ReentrantLock类内部有三个静态内部类Sync(NonfairSync和FairSync的基类)、NonfairSync(非公平锁)、FairSync(公平锁)
在看源码之前,看一下整体的架构图【根据上面的管程模型进行改动】:
3.1 ReentrantLock源码
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
// 在创建ReentrantLock对象的时候就指定是使用公平锁还是非公平锁。无参构造默认使用非公平锁。
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
// 调用内部静态类,根据参数是公平锁还是非公平锁
public void lock() {
sync.lock();
}
// 不管这个锁是公平的还是非公平的,都是按照非公平锁的方式获取资源方法
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
// 调用AQS类的tryAcquireNanos,如果等待时间比较久超过1000ns,就会让该线程进行阻塞。
// 这个会根据锁是公平锁还是非公平锁来获取资源,不同于tryLock()
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public void unlock() {
sync.release(1);
}
}
3.2 Sync静态内部类
abstract static class Sync extends AbstractQueuedSynchronizer {
// 抽象方法,等待实现类NonfairSync和FairSync实现。该方法被ReentrantLock.Lock()调用
abstract void lock();
// 非公平锁获取资源。直接就获取资源,只看是不是有资源(资源被其他线程占用)
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 如果资源没线程占用,则cas直接占用,占用成功直接返回,设置占用资源的线程为当前线程
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
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;
}
// 这个方法其实是AQS中的方法,lock.unlock()就会调用这个方法
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // 释放完资源唤醒队列线程
return true;
}
return false;
}
// 释放资源,释放资源不需要循环,只有获取资源才会竞争才需要CAS+自旋。释放不需要竞争
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); // 如果释放所有资源,把owner线程释放
}
setState(c);
return free;
}
}
3.3 NonfairSync非公平锁
一句话概述:当一个线程想要获取资源的时候,先争取去获取资源state,如果没竞争到再老老实实去阻塞队列中排队。
那为什么阻塞队列中有等待线程,state资源还可能为空呢?
有可能上一个线程刚释放,阻塞线程还没来的急抢占。
static final class NonfairSync extends Sync {
// 非公平锁在放入队列之前先判断是否可以获取到锁,如果能够获取到直接使用,否则加入阻塞队列
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
3.4 FairSync公平锁
一句话概述:当前线程获取锁资源的时候,抢占资源的条件是阻塞队列中没有等待线程。
static final class FairSync extends Sync {
// 直接加入阻塞队列
final void lock() {
acquire(1);
}
// 公平锁在获取资源的时候会先判断当前线程在队列中是否还存在前面的节点,即只有当前节点为第一个节点的时候才可以获取资源
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;
}
}
通过上述发现公平锁和非公平锁最大的区别就是:
- 不同点:公平锁即使当前资源空闲,只要阻塞队列中有线程等待就会乖乖去排队,而非公平锁则是只要资源闲着就去抢一下,抢不到再老老实实去阻塞队列排队。【公平锁像是一个懂得克制的乖孩子,非公平锁则是调皮捣蛋的小孩子,不管行不行,先抢他一下试试】
- 相同点:
- 非公平锁抢了一下没获取到资源,也需要去排队,只给你一次机会。
- 公平锁和非公平锁调用tryLock()都是非公平锁实现,直接看资源能不能获取到。