前言
JUC
并发包中提供了ReentrantReadWriteLock
作为读写锁,本篇文章将对读写锁的如下四个场景进行演示。
- 当前线程获取
读锁
时,读锁
是否被获取不会影响读锁
的获取; - 当前线程获取
读锁
时,若写锁
未被获取或者写锁
被当前线程获取,则允许获取读锁
,否则进入等待状态; - 当前线程获取
写锁
时,若读锁
已经被获取,无论获取读锁
的线程是否是当前线程,都进入等待状态; - 当前线程获取
写锁
时,若写锁
已经被其它线程获取,则进入等待状态。
正文
一. 场景一
本小节演示:当前线程获取读锁
时,读锁
是否被获取不会影响读锁
的获取。示例如下。
public class ReentrantReadWriteLockTest {
@Test
public void 当前线程获取读锁_读锁是否被获取不会影响读锁的获取() throws Exception {
// 创建读写锁
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
Lock readLock = reentrantReadWriteLock.readLock();
// 创建CountDownLatch对象
CountDownLatch countDownLatch = new CountDownLatch(2);
// 创建任务
Runnable readLockRunnable1 = new Runnable() {
@Override
public void run() {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 获取读锁");
Thread.sleep(1000);
} catch (InterruptedException e) {
// ignore
} finally {
readLock.unlock();
countDownLatch.countDown();
}
}
};
Runnable readLockRunnable2 = new Runnable() {
@Override
public void run() {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 获取读锁");
Thread.sleep(1000);
} catch (InterruptedException e) {
// ignore
} finally {
readLock.unlock();
countDownLatch.countDown();
}
}
};
// 线程1先获取读锁
Thread thread1 = new Thread(readLockRunnable1, "线程1");
thread1.start();
// 线程2再获取读锁
Thread thread2 = new Thread(readLockRunnable2, "线程2");
thread2.start();
// 阻塞主线程
countDownLatch.await();
}
}
上述示例中,演示了线程1和线程2先后获取读锁,如果获取到读锁,则打印一条日志。运行测试程序,结果如下。
运行结果表明,当前线程获取读锁
时,读锁
是否被获取不会影响读锁
的获取。
二. 场景二
本小节演示:当前线程获取读锁
时,若写锁
未被获取或者写锁
被当前线程获取,则允许获取读锁
,否则进入等待状态。示例如下。
public class ReentrantReadWriteLockTest {
@Test
public void 当前线程获取读锁_若写锁已经被当前线程获取则当前线程能获取到读锁() throws Exception {
// 创建读写锁
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
Lock readLock = reentrantReadWriteLock.readLock();
Lock writeLock = reentrantReadWriteLock.writeLock();
// 创建CountDownLatch对象
CountDownLatch countDownLatch = new CountDownLatch(1);
// 创建任务
Runnable runnable = new Runnable() {
@Override
public void run() {
// 先拿写锁
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 获取到了写锁");
// 同一线程中再拿读锁
if (readLock.tryLock()) {
try {
System.out.println(Thread.currentThread().getName() + " 获取到了读锁");
} finally {
readLock.unlock();
}
}
} finally {
writeLock.unlock();
countDownLatch.countDown();
}
}
};
// 创建线程
Thread thread = new Thread(runnable, "线程1");
thread.start();
// 阻塞主线程
countDownLatch.await();
}
}
上述示例中,演示了同一线程先后获取写锁和读锁,获取到对应锁后,打印日志。运行测试程序,结果如下所示。
运行结果表明,当前线程获取读锁
时,若写锁
被当前线程获取,则允许获取读锁
。
再看如下示例。
public class ReentrantReadWriteLockTest {
@Test
public void 当前线程获取读锁_若写锁已经被其它线程获取则当前线程无法获取读锁() throws Exception {
// 创建读写锁
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
Lock readLock = reentrantReadWriteLock.readLock();
Lock writeLock = reentrantReadWriteLock.writeLock();
// 创建CountDownLatch对象
CountDownLatch countDownLatch = new CountDownLatch(2);
// 创建拿写锁的任务
Runnable writeLockRunnable = new Runnable() {
@Override
public void run() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 获取到了写锁");
Thread.sleep(1000);
} catch (InterruptedException e) {
// ignore
} finally {
writeLock.unlock();
countDownLatch.countDown();
}
}
};
// 创建拿读锁的任务
Runnable readLockRunnable = new Runnable() {
@Override
public void run() {
if (readLock.tryLock()) {
try {
System.out.println(Thread.currentThread().getName() + " 获取到了读锁");
} finally {
readLock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() + " 获取读锁失败");
}
countDownLatch.countDown();
}
};
// 创建线程拿写锁
Thread thread1 = new Thread(writeLockRunnable, "线程1");
thread1.start();
// 创建线程拿读锁
Thread thread2 = new Thread(readLockRunnable, "线程2");
thread2.start();
countDownLatch.await();
}
}
上述示例中,演示了线程1先获取写锁,线程2后获取读锁,并打印获取结果。运行测试程序,结果如下。
运行结果表明:当前线程获取读锁
时,若写锁
已被其它线程获取,则获取失败。
三. 场景三
本小节演示:当前线程获取写锁
时,若读锁
已经被获取,无论获取读锁
的线程是否是当前线程,都进入等待状态。示例如下。
public class ReentrantReadWriteLockTest {
@Test
public void 当前线程获取写锁_若读锁已被当前线程获得则当前线程无法获取写锁() {
// 创建读写锁
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
Lock readLock = reentrantReadWriteLock.readLock();
Lock writeLock = reentrantReadWriteLock.writeLock();
// 创建CountDownLatch对象
CountDownLatch countDownLatch = new CountDownLatch(1);
// 创建任务
Runnable runnable = new Runnable() {
@Override
public void run() {
// 先拿读锁
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " 获取到了读锁");
// 同一线程中再拿写锁
if (writeLock.tryLock()) {
try {
System.out.println(Thread.currentThread().getName() + " 获取到了写锁");
} finally {
writeLock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() + " 获取写锁失败");
}
} finally {
readLock.unlock();
countDownLatch.countDown();
}
}
};
// 创建线程并执行任务
Thread thread = new Thread(runnable, "线程1");
thread.start();
// 阻塞主线程
countDownLatch.countDown();
}
}
上述示例中,演示了同一线程先获取读锁,再获取写锁,并打印获取结果。运行测试程序,结果如下。
运行结果表明:当前线程获取写锁
时,若读锁
已经被获取,无论获取读锁
的线程是否是当前线程,都获取失败。
四. 场景四
本小节演示:当前线程获取写锁
时,若写锁
已经被其它线程获取,则进入等待状态。示例如下。
public class ReentrantReadWriteLockTest {
@Test
public void 当前线程获取写锁_若写锁已经被其它线程获取则获取失败() throws Exception {
// 创建读写锁
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
Lock writeLock = reentrantReadWriteLock.writeLock();
// 创建CountDownLatch对象
CountDownLatch countDownLatch = new CountDownLatch(2);
// 创建拿写锁的任务
Runnable writeLockRunnable = new Runnable() {
@Override
public void run() {
if (writeLock.tryLock()) {
try {
System.out.println(Thread.currentThread().getName() + " 获取到了写锁");
Thread.sleep(1000);
} catch (InterruptedException e) {
// ignore
} finally {
writeLock.unlock();
}
} else {
System.out.println(Thread.currentThread().getName() + " 获取写锁失败");
}
countDownLatch.countDown();
}
};
// 创建线程1,拿写锁
Thread thread1 = new Thread(writeLockRunnable, "线程1");
thread1.start();
// 创建线程2,拿写锁
Thread thread2 = new Thread(writeLockRunnable, "线程2");
thread2.start();
// 阻塞主线程
countDownLatch.await();
}
}
上述示例中,演示了线程1先获取写锁,线程2后获取写锁,并打印获取结果。运行测试程序,结果如下。
上述结果表明:当前线程获取写锁
时,若写锁
已经被其它线程获取,则获取失败。
总结
读写锁ReentrantReadWriteLock
的使用流程通常如下。
- 创建
ReentrantReadWriteLock
对象; - 通过
ReentrantReadWriteLock
的readLock()
方法创建读锁ReentrantReadWriteLock.WriteLock
; - 通过
ReentrantReadWriteLock
的writeLock()
方法创建写锁ReentrantReadWriteLock.ReadLock
; - 后续加
读锁
通过ReentrantReadWriteLock.WriteLock
,加写锁
通过ReentrantReadWriteLock.ReadLock
。
读写锁的使用规则总结如下。
- 当前线程获取
读锁
时,读锁
是否被获取不会影响读锁
的获取; - 当前线程获取
读锁
时,若写锁
未被获取或者写锁
被当前线程获取,则允许获取读锁
,否则进入等待状态; - 当前线程获取
写锁
时,若读锁
已经被获取,无论获取读锁
的线程是否是当前线程,都进入等待状态; - 当前线程获取
写锁
时,若写锁
已经被其它线程获取,则进入等待状态。
读锁和写锁加锁时有两种方式。
- 通过
lock()
方法加锁,失败则进入同步队列等待; - 通过
tryLock()
方法加锁,成功返回true,失败返回false。