之前学习了synchronized关键字,它是用于对 对象 加锁 实现同步。https://blog.csdn.net/wenqiao1/article/details/108385759
java1.5后新增了Lock,ReadWriteLock接口以及一些实现了lock接口的类;它也是用于实现各种锁(可重入锁,读写锁)
之所以又新加了这些接口,是因为synchronized存在一些缺陷:
1. 当一个线程通过synchronized加上锁以后,其他的线程只能一直等待它释放锁 而不能做别的,而这个线程只有在三种情况下释放锁:
a. 代码块(方法)执行完了
b. 占有锁的线程发生了异常,JVM要求它释放锁
c. 占有锁的线程进入waiting状态而释放锁,例如在代码块(方法)中调用wait()方法
针对这个缺陷,Lock提供了tryLock()方法,能够使线程在获得不到锁的时候,等待一段时间 如果等不到就中断
此外,它还提供了lockInterruptibly()方法,去中断线程。
2. 当多个线程对同一个文件进行读操作的时候,多个线程同时读 实际上并不会发生冲突。但如果使用synchroized关键词,每个
线程都会上锁直到读完,而不会使线程们一起读取,这样消耗了时间。这时候ReadWriteLock接口的优势就体现出来了。
3. 在使用synchronized关键字时,我们(程序员)无法得知某个线程是否获得了锁。但Lock可以
Lock的缺点:
需要程序员主动释放锁,即使发生异常也不会释放锁,一般会要求将释放锁的代码写入finally{}块中
Lock 接口中的方法:
lock() tryLock() tryLock(long time, TimeUnit unit) lockInterruptibly() 这四个方法都是用来获取锁的。
1. void lock() 获取锁,如果锁已经被其他线程获取,则线程要进行等待。
2. boolean tryLock() boolean tryLock(long time, TimeUnit unit)
这两个方法是有返回值的,他们尝试获得锁,如果获取成功,则返回true;否则返回false
boolean tryLock(long time, TimeUnit unit) 会在没获取锁之后 等待一定义时间,如果在期限内如果拿不到锁,就返回false
3. void lockInterruptibly() throws InterruptedException;
通过这个方法获取锁的时候,如果锁可用,会获得锁并立即返回true;否则会休眠一直到这两种事件之一发生:
a. 等到了锁
b. 有其他的线程中断了该这个线程 (threadB.interrupt()): 注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。因为interrupt()方法只能中断阻塞过程中的线程而不能中断正在运行过程中的线程。因此,当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,那么只有进行等待的情况下,才可以响应中断的。
4. void unlock() 释放锁
5. Condition newCondition()
实现了Lock接口的类:ReentrantLock
这个锁时可重入锁。在创建对象的时候通过参数决定它是否为公平锁,默认为非公平的。(synchronized为非公平的)
ReentrantLock实际上主要是AQS实现的,这个类有一个成员变量sync,它是Sync类型的,而Sync是一个ReentrantLock内部类,它继承了AQS,因为ReentrantLock是独占锁,所以它实现了AQS的tryRelease()方法。
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; //返回资源是否真的被释放;有可能只是重入的线程释放了内层的锁
}
但这个类依然是一个抽象类;它有两个子类:fairSync 和 NonfairSync。刚刚提到sync实现了AQS的tryRelease方法,那获取资源的方法在哪儿实现的呢?就是在这两个子类中,分别实现了公平和非公平时的获取资源的方法 tryAcquire()。且这两个类只实现了这一个方法。也就是说不论锁是公平锁还是非公平锁,释放锁的代码都是一样的,但是获得锁是不一样的。
获得锁的代码差距其实只是在state==0的时候,公平锁需要调用 AQS 的 hasQueuedPredecessors() 方法,这个方法判断了队列中是否有其他的线程在该线程之前,如果有则返回true;按照对来顺序来说,当前的这个线程不能获取锁,因为还没轮到它。如果该线程是头一个或者队列为空,则返回false
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires); //调用了父类的方法
}
}
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
@ReservedStackAccess
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState(); //查看现在是否有线程获得锁
if (c == 0) {//如果没有 通过CAS修改state的值,因为用户只能调用lock(),因此acquires一定为1
if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
// 公平和非公平的区别就是判断 hasQueuedPredecessors
setExclusiveOwnerThread(current);
return true; //有返回值,但是lock()方法不会处理这个返回值
}
}
else if (current == getExclusiveOwnerThread()) {
//如果已经被占用了,因为是可重入锁,所以判断一下是不是获取所得线程重入
int nextc = c + acquires;//如果是,再给state+1
if (nextc < 0) //重入次数太多了,overflow了
throw new Error("Maximum lock count exceeded");
setState(nextc); //走到这儿也不会有线程竞争啦,因为只要是其他线程,就被if判断拦住了
return true;
}
return false;
}
}
// 这是Sync的方法,非公平锁占用锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
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;
}
public final boolean hasQueuedPredecessors() {
Node h, s;
if ((h = head) != null) {
if ((s = h.next) == null || s.waitStatus > 0) {
s = null; // traverse in case of concurrent cancellation
for (Node p = tail; p != h && p != null; p = p.prev) {
if (p.waitStatus <= 0)
s = p;
}
}
if (s != null && s.thread != Thread.currentThread())
return true;
}
return false;
}
接着讨论ReentrantLock(),它有一个成员函数 private final Sync sync; 当ReenrantLock()的构造函数没有参数时,会默认将sync初始化为非公平的AQS(NonfairSync);如果有参数,参数为true时就是公平的,否则是非公平的
因为AQS中有一个重要的成员变量 state,ReentrantLock也继承了这个变量。在ReentrantLock中,state为0时表示目前没有线程占用资源,为正数时表示有线程占用资源。因为这是独占模式的,所以当state大于1时,说明占用资源的线程重入了。
因为ReentrantLock实现了Lock接口,所以他要实现Lock接口中的方法。 回忆一下:acquire方法 中先调用tryAcquire,然后如果获取锁失败,调用acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 将线程入队列,一直等待获取锁;所以lock方法在线程没有获取锁的时候一直尝试和等待获取锁
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
首先是 lock和unlock,可以看出这都是通过调用sync(AQS)的 acquire 和 release方法实现的。
再之后是两个tryLock方法:
没有参数的tryLock方法在尝试获取锁失败后就会返回,因此这里没有调用AQS的acquire方法,因为acquire方法会调用acquireQueued()方法将线程入队列 且等待。只是调用nonfairTryAcquire尝试一次,获取不到就放弃 返回false
有参数的tryLock我还没有学到
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
ReadWriteLock接口 // 链接一个很好的博客https://www.cnblogs.com/xiaoxi/p/9140541.html
这个接口一共就俩方法:Lock readLock() Lock writeLock(),它们分别返回一个读锁和写锁。获得锁之后,用户可以调用读锁和写锁的方法。
ReentrantReadWriteLock 类实现了ReadWriteLock接口,这个类中也有一些内部类,比如Sync类(继承了AQS)、fairSync(公平锁),nonFairLock(非公平锁),ReadLock和WriteLock。
abstract static class Sync extends AbstractQueuedSynchronizer {}
static final class NonfairSync extends Sync {}
static final class FairSync extends Sync {}
public static class ReadLock implements Lock, java.io.Serializable {}
public static class WriteLock implements Lock, java.io.Serializable {}
线程进入读锁的前提条件:
没有其他线程的写锁,
没有写请求或者有写请求,但调用线程和持有锁的线程是同一个。
线程进入写锁的前提条件:
没有其他线程的读锁
没有其他线程的写锁
Sync是一个实现了AQS的抽象类。首先它里边有一个内部类 HoldCounter,它主要有两个属性,count和tid,其中count表示某个读线程重入的次数,tid表示该线程的tid字段的值,该字段可以用来唯一标识一个线程。
// 计数器
static final class HoldCounter {
// 计数
int count = 0;
// Use id, not reference, to avoid garbage retention
// 获取当前线程的TID属性的值
final long tid = getThreadId(Thread.currentThread());
}
这个Sync中,state(int类型,32位)记录了读锁和写锁的个数。同步状态在重入锁的实现中是表示被同一个线程重复获取的次数,即一个整形变量来维护,但是之前的那个表示仅仅表示是否锁定,而不用区分是读锁还是写锁。而读写锁需要在同步状态(一个整形变量)上维护多个读线程和一个写线程的状态。
读写锁对于同步状态的实现是在一个整形变量上通过“按位切割使用”:将变量切割成两部分,高16位表示读,低16位表示写。
和state有关的一些其他的成员变量:
abstract static class Sync extends AbstractQueuedSynchronizer {
// 版本序列号
private static final long serialVersionUID = 6317671515068378041L;
// 高16位为读锁,低16位为写锁
static final int SHARED_SHIFT = 16;
// 读锁单位
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
// 读锁最大数量
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
// 写锁最大数量
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
// 本地线程计数器
private transient ThreadLocalHoldCounter readHolds;
// 缓存的计数器
private transient HoldCounter cachedHoldCounter;
// 第一个读线程
private transient Thread firstReader = null;
// 第一个读线程的计数
private transient int firstReaderHoldCount;
}
ReadLock 和 WriteLock中都有一个AQS,这两个类分别实现了 tryAcquire() tryRelease() 以及 tryAcquireShared() tryReleaseShared() 方法。因为这两个类都继承了Lock接口,因此它们也需要实现Lock接口中的5个方法(不回忆了哈)
首先是 lock() 和 unlock() 方法,它俩其实和之前的ReentrantLock一样,都是调用AQS的方法实现的
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
首先先看写锁:
tryAcquire:
protected final boolean tryAcquire(int acquires) {
//当前线程
Thread current = Thread.currentThread();
//获取状态
int c = getState();
//写线程数量(即获取独占锁的重入数)
int w = exclusiveCount(c);
//其中exclusiveCount方法表示占有写锁的线程数量,源码如下:
//static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
//当前同步状态state != 0,说明已经有其他线程获取了读锁或写锁
if (c != 0) {
// 当前state不为0,此时:如果写锁状态为0说明读锁此时被占用返回false;
// 如果写锁状态不为0且写锁没有被当前线程持有返回false
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//判断同一线程获取写锁是否超过最大次数(65535),支持可重入
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//更新状态
//此时当前线程已持有写锁,现在是重入,所以只需要修改锁的数量即可。
setState(c + acquires);
return true;
}
//到这里说明此时c=0,读锁和写锁都没有被获取
//writerShouldBlock表示是否阻塞; 公平锁和非公平锁是不一样的
if (writerShouldBlock() || !compareAndSetState(c, c + acquires))
return false;
//设置锁为当前线程所有
setExclusiveOwnerThread(current);
return true;
}
tryRelease:
protected final boolean tryRelease(int releases) {
//若锁的持有者不是当前线程,抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//写锁的新线程数
int nextc = getState() - releases;
//如果独占模式重入数为0了,说明独占模式被释放
boolean free = exclusiveCount(nextc) == 0;
if (free)
//若写锁的新线程数为0,则将锁的持有者设置为null
setExclusiveOwnerThread(null);
//设置写锁的新线程数
//不管独占模式是否被释放,更新独占重入数
setState(nextc);
return free;
}
读锁:
protected final boolean tryRelease(int releases) {
//若锁的持有者不是当前线程,抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//写锁的新线程数
int nextc = getState() - releases;
//如果独占模式重入数为0了,说明独占模式被释放
boolean free = exclusiveCount(nextc) == 0;
if (free)
//若写锁的新线程数为0,则将锁的持有者设置为null
setExclusiveOwnerThread(null);
//设置写锁的新线程数
//不管独占模式是否被释放,更新独占重入数
setState(nextc);
return free;
}
protected final int tryAcquireShared(int unused) {
// 获取当前线程
Thread current = Thread.currentThread();
// 获取状态
int c = getState();
//如果写锁线程数 != 0 ,且独占锁不是当前线程则返回失败,因为存在锁降级
if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
return -1;
// 读锁数量 源码如下:
// static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
int r = sharedCount(c);
/*
* readerShouldBlock():读锁是否需要等待(公平锁原则)
* r < MAX_COUNT:持有线程小于最大数(65535)
* compareAndSetState(c, c + SHARED_UNIT):设置读取锁状态
*/
// 读线程是否应该被阻塞、并且小于最大值、并且比较设置成功
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
//r == 0,表示第一个读锁线程,第一个读锁firstRead是不会加入到readHolds中
if (r == 0) { // 读锁数量为0
// 设置第一个读线程
firstReader = current;
// 读线程占用的资源数为1
firstReaderHoldCount = 1;
} else if (firstReader == current) { // 当前线程为第一个读线程,表示第一个读锁线程重入
// 占用资源数加1
firstReaderHoldCount++;
} else { // 读锁数量不为0并且不为当前线程
// 获取计数器
HoldCounter rh = cachedHoldCounter;
// 计数器为空或者计数器的tid不为当前正在运行的线程的tid
if (rh == null || rh.tid != getThreadId(current))
// 获取当前线程对应的计数器
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0) // 计数为0
//加入到readHolds中
readHolds.set(rh);
//计数+1
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
// 在tryAcquireShared函数中,如果下列三个条件不满足(读线程是否应该被阻塞、小于最大值、比较设置成功)
// 则会进行fullTryAcquireShared函数中,它用来保证相关操作可以成功。
}
protected final boolean tryReleaseShared(int unused) {
// 获取当前线程
Thread current = Thread.currentThread();
if (firstReader == current) { // 当前线程为第一个读线程
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1) // 读线程占用的资源数为1
firstReader = null;
else // 减少占用的资源
firstReaderHoldCount--;
} else { // 当前线程不为第一个读线程
// 获取缓存的计数器
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) // 计数器为空或者计数器的tid不为当前正在运行的线程的tid
// 获取当前线程对应的计数器
rh = readHolds.get();
// 获取计数
int count = rh.count;
if (count <= 1) { // 计数小于等于1
// 移除
readHolds.remove();
if (count <= 0) // 计数小于等于0,抛出异常
throw unmatchedUnlockException();
}
// 减少计数
--rh.count;
}
for (;;) { // 无限循环
// 获取状态
int c = getState();
// 获取状态
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc)) // 比较并进行设置
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
return nextc == 0;
}
}