参考博客:Java中ReentrantLock的使用(优快云)
参考博客:Java并发编程之ReentrantReadWriteLock详解(优快云)
参考博客:ReentrantReadWriteLock读写锁详解(博客园)
参考博客:ReentrantReadWriteLock读写锁详解(知乎)
ReentrantLock 可重入锁实现了 Lock 接口,是一种独占锁。公平与否取决于构造函数。
ReentrantReadWriteLock 可重入锁实现了 ReadWriteLock 接口,ReentrantReadWriteLock 读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。公平与否取决于构造函数。
ReentrantReadWriteLock 中有两个内部静态类 ReadLock 和 WriteLock,他们都实现了Lock接口。
Lock接口中的方法:
// 不可中断的方式获得锁,获得锁后线程继续执行,否则阻塞,无法被interrput()方法中断。
void lock();
// 可中断的方式获得锁,获得锁后线程继续执行,否则阻塞,可以被interrput()方法中断。
void lockInterruptibly() throws InterruptedException;
// 尝试获得锁,获得锁以后返回ture,获取锁失败返回false。
boolean tryLock();
// 尝试获得锁,获得锁以后返回ture,超时以后返回false。
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 释放锁。
void unlock();
// 创建Condition,用于多线程间通信。
Condition newCondition();
ReentrantLock中除实现Lock接口外的一些其他方法:
// 构造函数,创建一个非公平锁。
ReentrantLock()
// 构造函数,fair = true:创建一个公平锁;fair = false:创建一个非公平锁。
ReentrantLock(boolean fair)
// 查看正在抢锁的线程数量。
int getQueueLength()
// 是否有线程正在抢锁。
boolean hasQueuedThreads()
// 指定的线程是否在抢锁。
boolean hasQueuedThread(Thread thread)
// 当前线程是否抢到锁。返回0代表没有。
int getHoldCount()
// 当前是否有某个线程占有锁。
boolean isLocked()
// 是否为公平锁
boolean isFair()
ReentrantLock.Condition是在粒度和性能上都优于Object的notify()、wait()、notifyAll()线程通信的方式。
Condition中通信方法相对Object的通信在粒度上是粒度更细化,表现在一个Lock对象上引入多个Condition监视器、通信方法中除了和Object对应的三个基本函数外,更是新增了线程中断、阻塞超时的函数;
Condition中通信方法相对Object的通信在性能上更高效,性能的优化表现在ReentrantLock比较synchronized的优化 ;
注意在使用Condition的await()、signal()、signalAll()方法时不能和Object的wait()、notify()、notifyAll()混用,否则抛出IllegalMonitorStateException;
Condition接口中的一些方法:
// 当前线程进入阻塞状态,允许其他线程抢占当前线程持有的lock锁。
// 可以被interrupt()方法中断,抛出InterruptedException异常,捕获异常后线程继续执行。
// 可以被signal()方法或者signalAll()方法唤醒,线程继续执行。
void await() throws InterruptedException;
// 当前线程进入阻塞状态,允许其他线程抢占当前线程持有的lock锁。
// 不可以被interrupt()方法中断。
// 可以被signal()方法或者signalAll()方法唤醒,线程继续执行。
void awaitUninterruptibly();
// 当前线程进入阻塞状态,允许其他线程抢占当前线程持有的lock锁。
// 可以被interrupt()方法中断,抛出InterruptedException异常,捕获异常后线程继续执行。
// 可以被signal()方法或者signalAll()方法唤醒,线程继续执行,返回nanosTimeout减去实际等待的时间(单位:纳秒)(一般为正值)
// 阻塞时间到期后,线程继续执行,返回nanosTimeout减去实际等待的时间(单位:纳秒)(一般为负值)
long awaitNanos(long nanosTimeout) throws InterruptedException;
// 当前线程进入阻塞状态,允许其他线程抢占当前线程持有的lock锁。
// 可以被interrupt()方法中断,抛出InterruptedException异常,捕获异常后线程继续执行。
// 可以被signal()方法或者signalAll()方法唤醒,线程继续执行,返回true。
// 阻塞时间到期后,线程继续执行,返回false。
boolean await(long time, TimeUnit unit) throws InterruptedException;
// 类似await(long time, TimeUnit unit)
boolean awaitUntil(Date deadline) throws InterruptedException;
// 唤醒指定线程。
void signal();
// 唤醒全部线程。
void signalAll();
ReentrantLock 使用 Demo:
static Integer i = 1;
public static void main(String[] args) {
// 定义锁
ReentrantLock lock = new ReentrantLock();
// 用于多线程间通信
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Thread thread1 = new Thread(() -> {
String name = Thread.currentThread().getName();
System.out.println(name + " 线程开始执行。");
try {
lock.lock();
System.out.println(name + " 线程获得锁。");
for (; i <= 6;) {
for (int j = 1; j <= 2100000000; j++) {
if (j == 2100000000) {
System.out.println(name + " 线程完成了第 " + i + " 次伟大的循环。");
}
}
i++;
// 线程间通信非常耗费性能。一次线程间通信的时间大约会耗费3次伟大的循环的时间。
// 唤醒使用condition2阻塞的线程
condition2.signal();
// 使用condition1阻塞当前线程
condition1.await();
}
// 唤醒使用condition2阻塞的线程
condition2.signal();
} catch (InterruptedException e) {
System.out.println(name + " 线程阻塞被中断。");
} finally {
lock.unlock();
}
System.out.println(name + " 线程执行完毕。");
},"thread1");
Thread thread2 = new Thread(() -> {
String name = Thread.currentThread().getName();
System.out.println(name + " 线程开始执行。");
try {
lock.lock();
System.out.println(name + " 线程获得锁。");
for (; i <= 6;) {
for (int j = 1; j <= 2100000000; j++) {
if (j == 2100000000) {
System.out.println(name + " 线程完成了第 " + i + " 次伟大的循环。");
}
}
i++;
// 唤醒使用condition1阻塞的线程
condition1.signal();
// 使用condition2阻塞当前线程
condition2.await();
}
// 唤醒使用condition1阻塞的线程
condition1.signal();
} catch (InterruptedException e) {
System.out.println(name + " 线程阻塞被中断。");
} finally {
lock.unlock();
}
System.out.println(name + " 线程执行完毕。");
},"thread2");
thread1.start();
thread2.start();
}
ReentrantReadWriteLock 实现了 ReadWriteLock,其内部的一些属性、方法和内部类:
// 读锁
private final ReentrantReadWriteLock.ReadLock readerLock;
// 写锁
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;
// 默认创建非公平锁
public ReentrantReadWriteLock() {
this(false);
}
// fair = true:创建一个公平锁,fair = false:创建一个非公平锁。
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
// 获得写锁
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
// 获得读锁
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
// 是否公平
public final boolean isFair() {}
// 持有读锁的线程数量
public int getReadLockCount() {}
// 是否有线程持有写锁
public boolean isWriteLocked() {}
// 写锁是否由当前线程持有
public boolean isWriteLockedByCurrentThread() {}
// 当前线程获得写锁的数量(因为可重入),如果写锁未被当前线程持有,则为零
public int getWriteHoldCount() {}
// 当前线程获得读锁的数量(因为可重入),如果读锁未被当前线程持有,则为零
public int getReadHoldCount() {}
// 是否有线程正在尝试获得读锁或写锁
public final boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
// 指定线程是否正在尝试获得读锁或写锁
public final boolean hasQueuedThread(Thread thread) {
return sync.isQueued(thread);
}
// 等待获取写锁或读锁的线程数量(估计值)
public final int getQueueLength() {}
// 查询是否有线程正在等待与写锁相关联的给定条件
public boolean hasWaiters(Condition condition) {}
// 返回与写锁相关联的给定条件下等待的线程数的估计值
public int getWaitQueueLength(Condition condition) {}
// toString "写锁数量,读锁数量"
public String toString() {
int c = sync.getCount();
int w = Sync.exclusiveCount(c); // 独占锁数量
int r = Sync.sharedCount(c); // 共享锁数量
return super.toString() + "[Write locks = " + w + ", Read locks = " + r + "]";
}
// 指定线程的 ThreadId
static final long getThreadId(Thread thread) {
return UNSAFE.getLongVolatile(thread, TID_OFFSET);
}
/** 内部类 **/
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 {}
/** 受保护的方法,外部无法访问 **/
// 返回获得写锁或正在尝试获得写锁的线程。
protected Thread getOwner() {}
// 返回正在尝试获得写锁的线程的集合
protected Collection<Thread> getQueuedWriterThreads() {}
// 返回正在尝试获得读锁的线程的集合
protected Collection<Thread> getQueuedReaderThreads() {}
// 返回正在尝试获得写锁或读锁的线程的集合
protected Collection<Thread> getQueuedThreads() {}
// 返回与写锁相关联的给定条件下等待的线程的集合
protected Collection<Thread> getWaitingThreads(Condition condition) {}
ReentrantReadWriteLock 的两个内部类:ReadLock 和 WriteLock 都实现了 Lock 接口,拥有 Lock 接口的方法。并且ReadLock 和 WriteLock 两个内部类中都存在 Sync sync 属性,ReadLock 和 WriteLock 重写 Lock 接口的方法时都是在操作 sync 对象。Sync 是ReentrantReadWriteLock 的一个内部抽象类,他还有两个子类:FairSync(公平锁)NonfairSync(非公平所)
读锁 ReadLock 是一种共享锁,允许多线程进入;但是如果某个线程获得了写锁,或者正在调用写数据的请求,其余线程不可进入读锁;如果当前线程获得了写锁,当前线程可以进入读锁。
写锁 WriteLock 是一种排它锁,只允许一个线程进入;如果其他线程进入了读锁或者写锁,当前线程不可进入写锁;如果当前线程进入了读锁,当前线程不可进入写锁。
ReentrantReadWriteLock 的可重入性表现在:读线程在获得了读锁后可以继续获得读锁,写线程在获得了写锁以后可以继续获得写锁或读锁。ReentrantReadWriteLock 允许写锁降级为读锁(获取写锁后,再获得读锁,然后释放写锁),但是读锁不能升级为写锁。写锁可以获得 Conditon 进行线程间通信,读锁不可以获得 Conditon 进行线程间通信,readLock().newCondition() 会抛出 UnsupportedOperationException。
ReentrantReadWriteLock 适用于读取数据频率较高但写数据频率低,然而写数据又对读数据有影响的情况下。
ReentrantReadWriteLock 使用 Demo:
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static WriteLock writeLock = rwl.writeLock();
static ReadLock readLock = rwl.readLock();
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i=1; i <= 2; i++) {
for (int j = 1; j <= 2100000000; j++) {
if (j == 2100000000) {
System.out.println(name + " 线程正在执行。");
}
}
}
// 获取读锁
readLock.lock();
for (int i=1; i <= 2; i++) {
for (int j = 1; j <= 2100000000; j++) {
if (j == 2100000000) {
System.out.println(name + " 线程持有读锁,正在读取数据。是否有线程持有写锁:" + rwl.isWriteLocked());
}
}
}
// 获取读锁后可重入读锁
getData(name);
// 释放读锁
readLock.unlock();
// 获取写锁
writeLock.lock();
for (int i=1; i <= 2; i++) {
for (int j = 1; j <= 2100000000; j++) {
if (j == 2100000000) {
System.out.println(name + " 线程持有写锁,正在写数据。持有读锁的线程数量:" + rwl.getReadLockCount() + ",是否有线程持有写锁:" + rwl.isWriteLocked());
}
}
}
// 获取写锁后可重入读锁
getData(name);
// 获取写锁后可重入写锁
setData(name);
System.out.println(name + " 线程写锁准备降级为读锁");
// 获取读锁
readLock.lock();
// 释放写锁,实现写锁降级为读锁
writeLock.unlock();
System.out.println(name + " 线程写锁已经降级为读锁");
for (int j = 1; j <= 2100000000; j++) {
if (j == 2100000000) {
System.out.println(name + " 线程持有读锁,正在读取数据。是否有线程持有写锁:" + rwl.isWriteLocked());
}
}
// 释放读锁
readLock.unlock();
}
};
new Thread(runnable,"thread1").start();
new Thread(runnable,"thread2").start();
new Thread(runnable,"thread3").start();
new Thread(runnable,"thread4").start();
}
// 读取数据,使用读锁
public static Object getData(String name) {
readLock.lock();
for (int j = 1; j <= 2100000000; j++) {
if (j == 2100000000) {
System.out.println(name + " 线程持有getData()读锁,正在读数据。是否有线程持有写锁:" + rwl.isWriteLocked());
}
}
readLock.unlock();
return null;
}
// 写数据,使用写锁
public static void setData(String name) {
writeLock.lock();
for (int j = 1; j <= 2100000000; j++) {
if (j == 2100000000) {
System.out.println(name + " 线程持有setData()写锁,正在写数据。持有读锁的线程数量:" + rwl.getReadLockCount() + ",是否有线程持有写锁:" + rwl.isWriteLocked());
}
}
writeLock.unlock();
}