重入锁ReentrantLock,即支持重进入的锁,它表示同一线程能够重复对资源(锁)进行加锁操作。此外,ReentrantLock还有公平和非公平之分。
设想这样一种情况:当独占式锁不支持重入时,在我们已经获得锁的情况下,在同一个线程再次获取该锁,此时获取锁失败并把当前线程加入同步队列中,即线程被自己阻塞。而重入锁就是为解决这个问题的。
先请求锁的线程先获得锁,则锁是公平锁;反之,是不公平锁。事实上,公平锁的效率没有非公平锁高。
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; } 复制代码
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; } 复制代码
该方法用于获取公平锁,与用于获取非公平锁的方法nonfairTryAcquire(int acquire)区别在于公平锁代码里的判断条件 if (compareAndSetState(0, acquires))
变成了非公平锁代码的 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires))
,即增加了方法hasQueuedPredecessors(),该方法用于判断当前节点是否有前驱节点,若返回true,说明有其他线程排在当前节点前边,根据公平锁的定义当前线程还需等待。
●读写锁包括 读锁 和 写锁 ,在同一时刻,读写锁允许多个线程获取读锁,但当线程获取写锁时,其他尝试获取读锁或写锁的线程都将阻塞,即写锁是排他锁。
●一般情况下,由于大多数场景读多于写,所以读写锁的性能会比排他锁好。
●Java并发包提供的读写锁实现是ReentrantReadWriteLock。
特性 | 说明 |
---|---|
公平性 | 该锁支持非公平(默认)和公平获取锁。 |
可重入 | 该锁是可重入锁。需要注意的是,获取写锁的线程可再次获取写锁,同时也可获取读锁。 |
锁降级 | 同一线程按顺序获取写锁、获取读锁、释放写锁,完成这一过程即称之锁降级。 |
读写锁是依赖同步器(AQS)为基础框架设计的,同步器维护了一个同步状态值(int型)来表示锁的获取与否。而读写锁包含了读锁和写锁,所以同步状态值的高16位表示读状态,低16位表示写状态。
●ReentrantReadWriteLock用于获取 写锁 的tryAcquire(int acquires)方法如下:
protected final boolean tryAcquire(int acquires) { Thread current = Thread.currentThread(); int c = getState(); int w = exclusiveCount(c); if (c != 0) { // (存在读锁或者当前线程不是已经获取写锁的线程) if (w == 0 || current != getExclusiveOwnerThread()) return false; if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); // Reentrant acquire setState(c + acquires); return true; } if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; setExclusiveOwnerThread(current); return true; } 复制代码
int w = exclusiveCount(c);
表示获取同步状态中表示写写状态的值(即低16位),若该值为0,说明读锁和写锁均没有被获取,因此写锁可直接被获;若该值不为0,说明读锁或者写锁已被获取,执行下一步。 if (w == 0 || current != getExclusiveOwnerThread())
中,w的值为0表示存在读锁,则写锁不能被获取,而对于"||"运算符来讲,当第一个判断条件为false时才会判断第二个条件,也就是说如果程序执行代码 current != getExclusiveOwnerThread()
表示不存在读锁且存在写锁,那么也就需要判断当前线程是否为已获取写锁的线程。 ●写锁释放时会减小写状态,当写状态的值为0时表示释放成功。
●读锁是一个支持重进入的共享锁,它能够被多个线程获取。当 写锁 没有被线程获取时, 读锁 总是能被任意线程获取到;当前线程尝试获取读锁时,如果写锁已被获取过,则当前线程进入等待状态。
●获取读锁的方法tryAcquireShared(int unused)如下( 获取读锁的实现复杂,这里为删减版本 )
protected final int tryAcquireShared(int unused) { for (;;) { int c = getState(); int nextc = c + (1 << 16); if (nextc < c) throw new Error("Maximum lock count exceeded"); if (exclusiveCount(c) != 0 && owner != Thread.currentThread()) return -1; if (compareAndSetState(c, nextc)) return 1; } } 复制代码
exclusiveCount(c)
表示获取同步变量的写状态,代码 exclusiveCount(c) != 0 && owner != Thread.currentThread()
表示写锁已被获取且当前线程不是获取写锁的线程,此时获取读锁失败。 ●锁降级指的是 当前线程 把持写锁,然后获取读锁,最后释放写锁的过程。
●锁降级的示例如下:
public void processData() { readLock.lock(); if (!update) { // 必须先释放读锁 readLock.unlock(); // 锁降级从写锁获取到开始 writeLock.lock(); try { if (!update) { // 准备数据的流程(略) update = true; } readLock.lock(); } finally { writeLock.unlock(); } // 锁降级完成,写锁降级为读锁 } try { // 使用数据的流程(略) } finally { readLock.unlock(); } } 复制代码